同样的输入,两个GCC编译器生成了不同的代码(第二个错误)。

5
我在使用GCC(版本为4.6.4,Ubuntu 12.04)编译一个大型项目(数百个文件和数十万行代码)时,遇到了一个奇怪的问题。最近我发现,在某些编译过程中(似乎是随机的),我会得到一段特定的代码编译结果与预期不同,导致我的代码出现未定义的行为:
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,setter似乎被内联了:

-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,即使清理构建文件夹后也会发生这种情况,但不一定只有在这种情况下才会发生。

您可能附近存在未定义的行为。 - kiwixz
6
这给我一种你正在违反一个定义规则的感觉,但我无法告诉你是如何以及以何种具体方式导致了你的问题。 - Mark B
4
太难猜了。我们可能需要一个MCVE(最小可重现示例)。 - AndyG
2
我会倾向于查看构建系统。是否有一些依赖项没有被捕捉到?你是否在运行并行构建(例如 make -j 4)?这种情况是否在执行 make clean 后发生过?等等... - Galik
4
我猜想您的 .h 文件被包含在多个 .cpp 文件中,但在其中一些文件中,有些内容会更改默认的类布局(例如 #pragma pack 或类成员定义不同),因此会生成不同版本的方法,链接器会随意选择其中一个版本。 - Matteo Italia
显示剩余2条评论
1个回答

5
如评论中所述,您可能在各个 .cpp 文件中以不同方式限制了类的定义,例如在包含 .h 文件之前使用 #pragma pack 或类似的东西;当链接器需要选择时,它可能会选择非确定性(因为它期望所有定义都相同)。
为了缩小问题根源的搜索范围,我建议您执行以下操作:
1. 使用调试符号(-g)编译整个项目; 2. 使用 gdb 确定每个模块中“有问题”字段的偏移量; 3. 找到不同值的位置后,可以使用 gcc -E 展开所有预处理器内容并查找问题。
作为第 2 步的辅助工具,您可以使用此 bash 一行命令(在目标文件所在的目录中运行):
for i in ./*.o; do echo -n "$i: "; gdb -batch -q "$i" -ex "print &((YourClass*)0)->yourField"; done

我遇到了一个问题。除了一个.o文件外,我得到了"No symbol "SomeClass" in current context." 的错误提示,但只有在 SomeClass.cpp.o 中,我获得了预期的结果,尽管定义我的有问题的类的 SomeClass.h 已经被包含在许多其他的 .cpp 文件中。我不知道是否值得一提,但我正在使用预编译头文件。 - QuantumBlack
预编译头文件确实给问题增加了另一个维度,但由于我从未使用过它们,所以无法确定它们如何影响此测试;也许您可以尝试在没有 PCH 的情况下构建并查看会发生什么? - Matteo Italia
很遗憾目前无法编译项目,因为它需要PCH并需要进行大量更改。但是我有各种#pragma packs,我会尝试删除它们,因为这是更快的解决方法。 - QuantumBlack
有点晚了,但问题仍然存在,即使使用GCC 6.2.0。我还删除了所有的#pragma pack,但没有帮助。还有其他想法吗? - QuantumBlack

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接