LINUX.ORG.RU

Что делать, если поле класса внутри и снаружи отличается?

 , ,


1

2

Не знаю, что и думать

Есть класс

class evolve                                                                    
{
    public:                                                                     
        float global_time;                                                      
}
Кроме этого есть другие поля и методы, конечно. Однако никакие другие поля не завязаны на global_time, а обращаются к этому полю лишь два метода: конструктор и operator(). Вот, как это происходит:
evolve::evolve(...): ..., global_time(0.0)
{
...
}

void evolve::operator()(float d_time)
{
...
global_time += d_time;
}       

Я создаю экземляр класса (назовём его ev), пытаюсь им пользоваться, но в ev.global_time находится какая-то каша: случайные данные, меняющиеся от запуска к запуску. Но не всё так просто: когда я изнутри класса вывожу для отладки global_time, то там правильное значение!

И это именно то самое поле, а не какая-то локальная переменная или иная путинца в именах, потому что

  • если изнутри класса печатать this->global_time, то точно так же всё ок, значение правильное
  • прошерстил весь проект с помощью grep по слову global_time — ничего лишнего нет.

Была гипотеза, что в каком-то месте в global_time записывается НЁХ, но и она не сработала - потому что в this->global_time лежат правильные данные всё время работы программы, в т.ч. и намного позже инициализации. А ev.global_time во внешнем коде выдаёт фигню сразу же после инициализации ev, и далее.

Все оптимизации отключил, толку 0.

Как такую дичь отлаживать вообще? И почему такое может быть, есть предположения?

★★★★★

Ответ на: комментарий от anonymous

А он такое может? Спасибо, попробую.

Никогда раньше не пользовался внешними инструментами отладки (кроме gdb core), но видимо день настал. Потому что мои любимые в таких случаях printf'ы тут бессильны.

Crocodoom ★★★★★
() автор топика

Скорее всего чтото с самим объектом ev не то, попробуйте вывести внутри класса printf(«%p %p\n», this, &(this->global_time)); И снаружи printf(«%p %p\n», &ev, &(ev.global_time));

zaz ★★★★
()

ev.global_time во внешнем коде

А мест использования много? Если не очень, то я бы сделал нормальный геттер для global_time и смотрел бы где компиляция сломается.

А может быть просто:

if (ev->global_time = some_other_garbage)

где-нибудь в незаметном месте.

dmiceman ★★★★★
()

брякаешься на создание ev и в gdb awatch *(float *)(&ev->global_time). ну и смотришь кто-там что пишет после конструктора

lberserq
()
Ответ на: комментарий от zaz
inside: 0x7fff211c6af0 0x7fff211c6e70
outside: 0x7fff211c6af0 0x7fff211c6ee8

Адреса объектов одинаковые, адреса полей разные... Что за чертовщина.

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

Адреса объектов одинаковые, адреса полей разные

Если код работы с обьектом в разных бинарниках - есть ненулевой шанс, что ты просто один из них не пересобрал после смены интерфейса.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

ты просто один из них не пересобрал после смены интерфейса.

Этот вариант отпадает, чистил много раз. Попробую разобраться с gdb тогда.

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

Если адреса и правда разные, приём с вотчпоинтами не поможет.

pon4ik ★★★★★
()
Ответ на: комментарий от Crocodoom

Если объектные файлы разные (которые пишут inside/outside) - то скорее всего хедер по разному интерпретируется в зависимости что компиляется. Возможно разные ключи сборки, возможно гдето влазить какойто include который все меняен (например настройки выравнивания полей #pragma pack, или переопределение какогото типа который используется в классе и в результате размещение полей плывет).

zaz ★★★★
()
Ответ на: комментарий от zaz

ага, уже сделал, как раз писал сообщение:)

Размеры объектов (т.е. ev и *this) тоже разные.

inside: 0x7ffcdc9899b0 0x7ffcdc989d30
inside: size = 904
outside: 0x7ffcdc9899b0 0x7ffcdc989da8
outside: size = 1024
Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от zaz

Да, объектные файлы действительно разные. Более того, класс evolve компилируется nvcc (это компилятор в PTX от Nvidia), а «юзер-код» обычным gcc. Впрочем, для хост-кода nvcc использует тот же самый gcc, и весь сюжет происходит на хосте, казалось бы должно быть всё нормально.

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

Разница довольно большая, если нету какихто специфичных полей внутри класса (которые могут интерпертироватся по разному) то попробуйте принудительно упаковать его

#pragma pack(push, 1);
class ....
{
};
#pragma pack(pop)

zaz ★★★★
()
Ответ на: комментарий от zaz

Упаковал, размеры чуть уменьшились, но остались разными, причём разница не изменилась.

inside: size = 900
outside: size = 1020
Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от zaz

Возможно разные ключи сборки, возможно гдето влазить какойто include который все меняен (например настройки выравнивания полей #pragma pack, или переопределение какогото типа который используется в классе и в результате размещение полей плывет).

Сейчас попробую остановить компиляцию на gcc -E для обоих случаев и почитать, что там в классе лежит.

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

Значит какойто тип имеет разный размер в зависимости от способа сборки

zaz ★★★★
()
Ответ на: комментарий от Crocodoom

Если разные размеры - может там наследование есть? (на это указывает что sizeof(*this) меньше)

+ я бы добавил в конструктор трассировку, вдруг он дважды вызывается?

inside: 0x7fff211c6af0 0x7fff211c6e70
outside: 0x7fff211c6af0 0x7fff211c6ee8

0x7fff211c6ee8-0x7fff211c6e70 = 120

inside: 0x7ffcdc9899b0 0x7ffcdc989d30
inside: size = 904
outside: 0x7ffcdc9899b0 0x7ffcdc989da8
outside: size = 1024

0x7ffcdc989da8 - 0x7ffcdc989d30 = 120

В обоих случаях откуда то берутся 120 байт лишних.

anonymous
()
Ответ на: комментарий от Crocodoom

Вероятно, nvcc указывает какие-то особые параметры компиляции, которые влияют на размер класса.

KivApple ★★★★★
()
Ответ на: комментарий от anonymous

каюсь, не сразу сообразил про nvcc и куду

посмотри сюда, там говорится что CUDA юзает особое выравнивание

https://stackoverflow.com/a/13783917

Если я правильно понял, то всё хреново, и у gcc не совпадает выравнивание с nvcc. Как рекомендовали, лучше сделай getter, который вернет тебе float* и работай через него :(

anonymous
()
Ответ на: комментарий от anonymous

2 zaz, 2 anon

-malign-double

Мне не помог этот ключ, к сожалению. Пока разбираюсь. За ссылку на stackoverflow спасибо.

Я нашёл поле класса evolve, у которого разнится размер. Это небольшой класс-логгер (там внутри есть cudaStream_t, cudaEvent_t и ещё по мелочи). Если я не смогу заставить gcc и nvcc «видеть» этот класс одинаково, то можно заюзать pimpl, как workaround.

Ещё есть тупой вариант использовать nvcc для всего (даже для файлов, где только хост-код), но к нему хочется прибегать в последнюю очередь, т.к. чревато проблемами в дальнейшем развитии функционала программы.

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

А если хранить только указатели на меняющиеся типы? И аллоцировать в конструкторе?

slapin ★★★★★
()
Ответ на: комментарий от Crocodoom

Раз -E можно передать, то я бы попробовал передать в nvcc -v и посмотреть параметры компилятора на предмет: -fshort-enums, -fshort-double, -fshort-wchar.

xaizek ★★★★★
()
Ответ на: комментарий от Crocodoom

Стремно это все както, можно наловить багов целый ворох. Я бы на вашем месте подумал бы о проксировании всех внутренних типов с полным скритием имлементации. Тоесть в вашем случае C like:

For GCC:
void* evolve_create();
float evolve_gloval_time(void *inst);

For nvcc:
void* evolve_create()
{
  return new evolve();
}

float evolve_gloval_time(void *inst)
{
  return reinterpret_cast<evolve*>(inst)->global_time;
}

Ну или на C++ как больше нравится, главное чтобы в общих хедерах были исключительно стандартные типы (int32/uint32/float/void*/char*)

zaz ★★★★
()
Ответ на: комментарий от Crocodoom

что мешает наделать геттеров? (жабисты так делают вообще всегда :)

stevejobs ★★★★☆
()

Это залёт

В общем, раскопал. Свёл к минимальному примеру.

Воспроизведите, пожалуйста, на 8-ой куде и gcc 6.3.0, у кого есть.

Или на любом другом окружении. Буду в багтрекер писать.

#include <string>                                                               
#include <cstdio>                                                               
                                                                                
std::string foo;                                                                
                                                                                
int main()                                                                      
{                                                                               
    printf("%ld\n", sizeof(foo));                                               
    return 0;                                                                   
}  
$ nvcc test.cpp && ./a.out 
nvcc warning : The 'compute_20', 'sm_20', and 'sm_21' architectures are deprecated, and may be removed in a future release (Use -Wno-deprecated-gpu-targets to suppress warning).
8
$ g++ test.cpp && ./a.out 
32
Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от xaizek

я бы попробовал передать в nvcc -v и посмотреть параметры компилятора на предмет: -fshort-enums, -fshort-double, -fshort-wchar.

У nvcc такого ключа нет, есть только --version, но там просто

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2016 NVIDIA Corporation
Built on Sun_Sep__4_22:14:01_CDT_2016
Cuda compilation tools, release 8.0, V8.0.44

Поэкспериментировал с -fshort-wchar и -fno-short-wchar, всё равно результаты (8, 32). Здесь что-то другое

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от zaz

Ну или на C++ как больше нравится, главное чтобы в общих хедерах были исключительно стандартные типы (int32/uint32/float/void*/char*)

Вот std::string чем не стандартный тип? Отож

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

помойму реализация std::string не описана в Стандарте. Реализация выбирает размер так, как ей больше нравится.

вот тут я забил твой код в clang, и он ответил вообще 24

не нужно писать такой код, который зависел бы от деталей реализации конпелятора

stevejobs ★★★★☆
()

Лол :-) Когда читаешь подобные вопросы, совсем не понятно, почему же за столько лет никогда не приходилось сталкиваться с чем-то подобным :-) Откуда такие проблемы вообще появляются - просто загадка :-) Лол :-)

anonymous
()

Что там уже можно сделать такого с этим бедным цепепе, чтобы напарываться на такие вот проблемы - ума не приложу :-) Это надо сильно стараться, наверное, в 2017 году то :-) Лол :-)

anonymous
()
Ответ на: Это залёт от Crocodoom

Фишка в том что в C++11 стандартом запретили COW строки, соотвецтвенно реализацию их полностью переписали и в GCC (по крайней мере в 5.4) есть две различные реализации std::string (одна старая, вторая новая) - новая используется при сборке со стандартом С++11 и выше также новый формат можно включить дефайном (-D_GLIBCXX_USE_CXX11_ABI) вобщем попробуйте разные стандарты С++ и дефайны, также советую посмотреть вот это:

https://gcc.gnu.org/onlinedocs/libstdc /manual/using_dual_abi.html

zaz ★★★★
()
Ответ на: Это залёт от Crocodoom

C вашим примером:

$ g++ test.cpp -D_GLIBCXX_USE_CXX11_ABI=0 && ./a.out
8

$ g++ test.cpp -D_GLIBCXX_USE_CXX11_ABI=1 && ./a.out
32

$ gcc --version
gcc (Gentoo 5.4.0-r3 p1.3, pie-0.6.5) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

zaz ★★★★
()
Последнее исправление: zaz (всего исправлений: 1)

А почему вообще член класса находится в public? Я бы убрал в private ради инкапсуляции и теста

XMs ★★★★★
()
Ответ на: комментарий от zaz

Наверное, лучшим вариантом здесь будет последовать вашему совету

главное чтобы в общих хедерах были исключительно стандартные типы (int32/uint32/float/void*/char*)

Вариант с «собирать всё одним компилятором» у меня просто не сработает, слишком уж раскудрявый проект.

Что ещё полезного нужно знать на тему межкомпиляторной линковки?

Всем спасибо за помощь!

Crocodoom ★★★★★
() автор топика

Я человек простой...

clang++ ... -fsanitize=memory -g ... 

Хотя силы телепатии может не хватить, по этому рекомендую посмотреть -fsanitize=address и -fsanitize=undefined

KennyMinigun ★★★★★
()
Последнее исправление: KennyMinigun (всего исправлений: 1)
Ответ на: комментарий от Crocodoom

Что ещё полезного нужно знать на тему межкомпиляторной линковки?

Если уже делать разгроничения на уровне линковщика, то нужно все что компилируется nvcc собирать в отдельную шаред либу (SO). Так как при статической линковке линковщик будет вычищать повторяющиеся символы (например std::string::length) и может оставить только один хотя их нужно будет два.

А вообще насщест std::string посмотрите два мои поста выше ...

zaz ★★★★
()
Ответ на: комментарий от zaz

nvcc на -D_GLIBCXX_USE_CXX11_ABI=1 или -D_GLIBCXX_USE_CXX11_ABI=0 никак не реагирует! Остаётся 8.

Видимо, у них там что-то своё. Всё-таки напишу на devtalk.nvidia

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

нечего туда писать

в данном случае достаточно заставить оба компилятора использовать одну и ту же стандартную библиотеку

anonymous
()
Ответ на: нечего туда писать от anonymous

Как это сделать?

Тут ещё такой вопрос, почему линкер не выдаёт никаких ошибок или хотя бы ворнингов? Можно ли его настроить так, чтобы выдавал? «Стандартные» ключи -Wall -Wextra -Werror тут не помогают, линкер молчит как партизан.

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от zaz

Можно как-то заставить линкер предупреждать о подобных неувязках в размерах «одинаковых» классов, etc? Чтобы он проверял совместимость?

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

nvcc на -D_GLIBCXX_USE_CXX11_ABI=1 или -D_GLIBCXX_USE_CXX11_ABI=0 никак не реагирует! Остаётся 8

nvcc - да, а вот заставить gcc использовать такйоже формат (чтобы получалось тоже 8) должно получится. Но тогда могут быть проблемы при подключении сторонних библиотек которые ожидают что std::string будет 32 ...

zaz ★★★★
()
Ответ на: комментарий от Crocodoom

Можно как-то заставить линкер предупреждать о подобных неувязках в размерах «одинаковых» классов, etc? Чтобы он проверял совместимость?

Без понятия, никогда таким вопросом не задавался ...

zaz ★★★★
()
Последнее исправление: zaz (всего исправлений: 1)
Ответ на: комментарий от zaz

Или лыжи не едут, или

Тут вот что выяснилось. Когда-то давно я прочитал, что nvcc можно указать хост-компилятор с помощью ключа -ccbin. И с тех всегда использовал его в своих проектах. А сейчас выясняется, что эта команда работает нифига не так, как можно ожидать:

$ g++ test.cpp && ./a.out
32
$ nvcc -ccbin=g++ test.cpp && ./a.out
8

Но

$ nvcc -ccbin=g++-6 test.cpp && ./a.out
32
То есть наконец-то выдает одинаковые размеры.

Хотя

$ g++ -v 2>&1 | tail -1
gcc version 6.3.0 20170516 (Debian 6.3.0-18) 
$ g++-6 -v 2>&1 | tail -1
gcc version 6.3.0 20170516 (Debian 6.3.0-18)

$ which g++
/usr/bin/g++
$ which g++-6
/usr/bin/g++-6

$ ls -la /usr/bin | grep g++-6 | head -1
lrwxrwxrwx  1 root   root           5 апр  8  2017 g++ -> g++-6

То есть g++ это символическая ссылка на g++-6. Но, блин, не для nvcc! Может криво настроен шелл или ещё что-то, я не знаю... У нас такими вещами занимается системный администратор. Или это всё же выкрутасы куды.

Crocodoom ★★★★★
() автор топика
Ответ на: комментарий от Crocodoom

А линкер знает что-то о классах и их размерах?

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.