Два компилятора GCC для одного входа, генерируются два разных кода (второй неверный)

У меня возникает странная проблема с GCC (4.6.4, Ubuntu 12.04), иногда я использую ее для компиляции огромного проекта (сотни файлов и сотни тысяч строк кода), но я недавно что-то заметил. После некоторых компиляций (кажется, случается случайным образом), я получаю определенный фрагмент кода, скомпилированный по-разному и ошибочно, вызывая поведение undefined в моем коде:

class someDerivedClass : public someBaseClass
{
    public:
        struct anotherDerived : public anoterBaseClass
        {
            void SomeMethod()
            {
                someMember->someSetter(2);
            }
        }
}

Где "someSetter" определяется как:

void someSetter(varType varName) { someOtherMember = varName; }

Обычно SomeMethod() скомпилируется в:

00000000019fd910  mov 0x20(%rdi),%rax 
00000000019fd914  movl $0x2,0x278c(%rax) 
00000000019fd91e  retq  

Но иногда он получает (неправомерно) скомпилированный для:

000000000196e4ee  mov 0x20(%rdi),%rax 
000000000196e4f2  movl $0x2,0x27d4(%rax) 
000000000196e4fc  retq  

Сетчатка, похоже, встраивается, вероятно, из-за флагов компиляции -O2:

-std=c++11 -m64 -O2 -ggdb3 -pipe -Wliteral-suffix -fpermissive -fno-fast-math -fno-strength-reduce -fno-delete-null-pointer-checks -fno-strict-aliasing

но это не проблема. Реальная проблема - это смещение члена someOtherMember, 0x278c правильно (первый случай), но 0x27d4 неверен (второй случай), и это, очевидно, заканчивается модификацией совершенно другого члена класса. Почему это происходит? Что мне не хватает? (также, я не знаю, какую другую соответствующую информацию я могу опубликовать, поэтому спросите). Помните, что это происходит при повторной компиляции проекта (полная перекомпиляция или просто компиляция только измененных файлов), без, изменение затронутого файла (или файлов с использованием используемых классов). Например, просто добавление простого printf() в полностью несвязанный файл может вызвать это поведение или заставить его уйти, когда это произойдет. Должен ли я просто обвинять это в -O2? Я не могу воспроизвести его без флага оптимизации, потому что это происходит абсолютно случайно. Я использую make -j 8, это происходит даже после очистки папки сборки, но не обязательно происходит только после этого

Ответ 1

Как указано в комментариях, у вас, вероятно, есть что-то, что определяет определение вашего класса по-разному в различных .cpp, например a #pragma pack или что-то подобное до включения вашего .h; когда компоновщик должен выбрать, он может выбирать не детерминистически (поскольку он ожидает, что все определения будут одинаковыми).

Чтобы сузить поиск корня проблемы, я бы сделал что-то вроде этого:

  • скомпилируйте весь проект с помощью отладочных символов (-g);
  • используйте gdb, чтобы определить, что является смещением "проблемного" поля в соответствии с каждым модулем.
  • когда вы найдете, где у вас разные значения, вы можете использовать gcc -E, чтобы развернуть все материалы препроцессора и искать свою проблему.

В качестве помощи для шага 2 вы можете использовать этот bash однострочный (для запуска в каталоге, где находятся объектные файлы):

for i in ./*.o; do echo -n "$i: "; gdb -batch -q "$i" -ex "print &((YourClass*)0)->yourField"; done