GCC:程序不适用于编译选项 -O3
Posted
技术标签:
【中文标题】GCC:程序不适用于编译选项 -O3【英文标题】:GCC: program doesn't work with compilation option -O3 【发布时间】:2008-11-11 05:09:42 【问题描述】:我正在编写一个 C++ 程序,当我使用优化(选项 -O1、-O2、-O3 等)编译它时它不起作用(我得到一个分段错误),但是当我使用它时它工作得很好编译它而不进行优化。
我的代码中有没有可能出现错误?还是我应该假设这是 GCC 中的错误?
我的 GCC 版本是 3.4.6。
对于此类问题是否有任何已知的解决方法?
我的程序的优化版和未优化版的速度差别很大,所以我真的需要使用优化。
这是我原来的仿函数。一个在没有优化级别的情况下工作正常,并且在任何级别的优化下都会引发分段错误:
struct distanceToPointSort
indexedDocument* point ;
distanceToPointSort(indexedDocument* p): point(p)
bool operator() (indexedDocument* p1,indexedDocument* p2)
return distance(point,p1) < distance(point,p2) ;
;
而且这个在任何级别的优化下都可以完美运行:
struct distanceToPointSort
indexedDocument* point ;
distanceToPointSort(indexedDocument* p): point(p)
bool operator() (indexedDocument* p1,indexedDocument* p2)
float d1=distance(point,p1) ;
float d2=distance(point,p2) ;
std::cout << "" ; //without this line, I get a segmentation fault anyways
return d1 < d2 ;
;
不幸的是,这个问题很难重现,因为它发生在一些特定的值上。在对一千多个向量中的一个进行排序时,我得到了分段错误,因此它实际上取决于每个向量具有的特定值组合。
【问题讨论】:
这几乎可以肯定是您的代码中的错误,而不是 GCC 中的错误。发布一些代码或更多信息以获得体面的答案。你在你的应用程序中到底在做什么,它是如何设计的,不仅仅是“它不适用于优化”:) SEGV 表示内存或指针问题,所以从那里开始 我叫巨魔。如果原帖者对编程有足够的了解来写这个问题,那么原帖者就知道问题的答案。还要考虑没有任何代码示例。 没有代码,我们没有机会帮助您。我害怕。 看到您添加的答案后,我很抱歉打电话给巨魔。请理解为什么它看起来是这样的。 【参考方案1】:既然您发布了代码片段并找到了可行的解决方法(@Windows 程序员的回答),我可以说您正在寻找的可能是 -ffloat-store
。
-float-存储
不要将浮点变量存储在寄存器中,并禁止其他可能改变浮点值是从寄存器还是内存中获取的选项。
此选项可防止在 68000 等机器上出现不必要的过度精度,其中浮动寄存器(68881 的)保持比双精度应有的精度更高。对于 x86 架构也是如此。对于大多数程序来说,超额精度只会带来好处,但少数程序依赖于 IEEE 浮点的精确定义。在修改它们以将所有相关的中间计算存储到变量中之后,对此类程序使用 -ffloat-store。
来源:http://gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/Optimize-Options.html
【讨论】:
【参考方案2】:我会首先假设您的代码是错误的。 虽然很难说。
您的代码编译时是否出现 0 个警告?
g++ -Wall -Wextra -pedantic -ansi
【讨论】:
【参考方案3】:这是一些似乎可以工作的代码,直到你点击 -O3...
#include <stdio.h>
int main()
int i = 0, j = 1, k = 2;
printf("%d %d %d\n", *(&j-1), *(&j), *(&j+1));
return 0;
没有优化,我得到“2 1 0”;通过优化我得到“40 1 2293680”。为什么?因为 i 和 k 被优化了!
但我正在获取 j 的地址并离开分配给 j 的内存区域。这是标准不允许的。您的问题很可能是由与标准的类似偏差引起的。
我发现valgrind 在这种情况下通常很有帮助。
编辑:一些评论者认为该标准允许任意指针算术。 它没有。请记住,有些架构有有趣的寻址方案,对齐可能很重要,如果溢出某些寄存器可能会出现问题!
[draft] 标准中关于向/从指针中添加/减去整数的内容(强调添加):
"如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则求值不会产生溢出;否则,行为未定义。"
由于 &j 甚至不指向数组对象,&j-1 和 &j+1 几乎不能指向同一个数组对象的一部分。因此,简单地评估 &j+1(更不用说取消引用它)是未定义的行为。
在 x86 上,我们可以确信向指针添加 1 是相当安全的,并且只会将我们带到下一个内存位置。在上面的代码中,当我们对内存包含的内容进行假设时,就会出现问题,标准当然不会接近。
【讨论】:
请将其编辑为“这里有一些错误的代码意外地在低于 -O3 的级别上工作”。当然你后来澄清了,但你的第一行误导了这个讨论的读者。 任何在 -O3 处失败的代码都是错误的代码,在 -O3 处意外运行。当然,除非它真的归结为编译器错误。这个例子只是特别明显地表明代码很糟糕。我认为这是故意说明这一点。 其实这是标准允许的。您只需创建一个指向 j 的指针,然后将指针减/加 1 并取消引用它。这是完全合法的,标准允许你尝试读取任何你喜欢的指针。 标准在哪里说明 k 正好是 j 之后的四个字节而不是 8? @Pax:“尝试读取您喜欢的任何指针”是未定义的行为。即使只是创建指向某个任意地址的指针也是未定义的行为。【参考方案4】:作为一个实验,尝试看看这是否会强制编译器一致地四舍五入。
volatile float d1=distance(point,p1) ;
volatile float d2=distance(point,p2) ;
return d1 < d2 ;
【讨论】:
多么丑陋的解决方案,但我很高兴有一个解决方案。 我仍然很好奇为什么输出一个空字符串(即在计算距离和返回比较之间做一些事情)解决了这个问题。 也许函数调用迫使 gcc 将 d1 和 d2 存储到内存中,而不是将它们保存在寄存器中。所以四舍五入会变得一致。 所以,可能您的解决方案和我的解决方案正在做同样的事情,即阻止编译器优化代码。可能是,如果没有 volatile 限定符(或中间的函数调用),GCC 会将代码转换为类似于我的第一个仿函数的东西。 尝试使用 -ffloat-store 而不是 volatile(有关详细信息,请参阅我的答案)。【参考方案5】:错误在您的代码中。根据 C 标准,您可能正在做一些调用未定义行为的事情,而这种行为恰好可以在没有优化的情况下工作,但是当 GCC 为执行其优化做出某些假设时,当这些假设不正确时,代码就会中断。确保使用-Wall
选项进行编译,-Wextra
也可能是一个好主意,看看你是否收到任何警告。您也可以尝试-ansi
或-pedantic
,但这些都可能导致误报。
【讨论】:
勇敢的呼吁(“错误在你的代码中”)但你可能是对的。此外,如果它不符合 C 标准,则应在编译时捕获,而不是在运行时转储内核。 "另外,如果它对 C 标准是非法的,它应该在编译时被捕获" -- 从标准输入读取一个 int,除以 int,并在编译时预测用户将键入零?喂? 这正是问题所在 - 它是合法的,但会产生未定义的行为。前者意味着编译器不必抱怨。后者是人们有时会混淆他们的术语的地方,当他们的意思是“无效”或“无聊”时说“非法”。 @onebyone:好吧,标准中根本没有提到“合法”,所以你的术语也好不到哪里去。如果我们必须迂腐,我们应该完全避免使用“合法”和“非法”。我倾向于使用“非法”作为“格式错误或依赖未定义行为”的总称,但就 C++ 而言,没有官方定义。【参考方案6】:您可能遇到了别名问题(或者可能是其他一百万个问题)。查找 -fstrict-aliasing 选项。
如果没有更多信息,这种问题是不可能正确回答的。
【讨论】:
-fstrict-aliasing 默认禁用 gcc 4.3 从 -O2 开始启用【参考方案7】:编译器的错误很少见,但编译器确实存在错误,并且它们经常在不同的优化级别上表现出来(例如,如果在优化过程中存在错误)。
通常在报告编程问题时:提供最小的代码示例来演示问题,这样人们就可以将代码保存到文件中,编译并运行它。尽可能轻松重现您的问题。
另外,尝试不同版本的 GCC(编译自己的 GCC 非常容易,尤其是在 Linux 上)。如果可能,请尝试使用其他编译器。英特尔 C 有一个或多或少兼容 GCC 的编译器(我认为,非商业用途免费)。这将有助于查明问题。
【讨论】:
【参考方案8】:它几乎(几乎)从来不是编译器。
首先,确保您使用 -Wall 进行无警告编译。
如果这没有给您带来“灵光乍现”的时刻,请将调试器附加到您崩溃的可执行文件的优化程度最低的版本,并查看它在做什么以及它去了哪里。
5 会给你 10 分你已经解决了这个问题。
【讨论】:
【参考方案9】:几天前遇到了同样的问题,在我的例子中是混叠。与其他编译器相比,GCC 的做法有所不同,但并没有错。 GCC 已经成为一些人可能称之为 C++ 标准的规则律师,它们的实现是正确的,但你也必须在你的 C++ 中真正正确,否则它会过度优化某些东西,这很痛苦。但是你得到了速度,所以不能抱怨。
【讨论】:
【参考方案10】:在阅读了一些 cmets 后,我希望在这里得到一些反对意见,但在主机游戏编程世界中,较高的优化级别有时会在奇怪的边缘情况下生成错误的代码,这是相当普遍的知识。不过,很可能可以通过对代码进行细微的更改来修复边缘情况。
【讨论】:
不投反对票,但您会得到与我在 Artelius 的回答中所做的相同的评论 - 如果编译器在 -O3 处生成错误代码,那么您的代码无效的可能性仍然很大,而不是存在错误编译器。但是很容易写出无效的C/C++代码。 也许,但如果大量游戏开发人员无法找出代码中的错误,并且代码可以 100% 正常工作,那么从技术上讲,这可能不是错误,但我会说 de实际上是一个错误。【参考方案11】:好吧... 这是我遇到过的最奇怪的问题之一。 我认为我没有足够的证据表明这是一个 GCC 错误,但老实说......它看起来真的很像。
这是我原来的仿函数。一个在没有优化级别的情况下工作正常,并且在任何级别的优化下都会引发分段错误:
struct distanceToPointSort
indexedDocument* point ;
distanceToPointSort(indexedDocument* p): point(p)
bool operator() (indexedDocument* p1,indexedDocument* p2)
return distance(point,p1) < distance(point,p2) ;
;
而且这个在任何级别的优化下都可以完美运行:
struct distanceToPointSort
indexedDocument* point ;
distanceToPointSort(indexedDocument* p): point(p)
bool operator() (indexedDocument* p1,indexedDocument* p2)
float d1=distance(point,p1) ;
float d2=distance(point,p2) ;
std::cout << "" ; //without this line, I get a segmentation fault anyways
return d1 < d2 ;
;
不幸的是,这个问题很难重现,因为它发生在一些特定的值上。在对一千多个向量中的一个进行排序时,我得到了分段错误,因此它实际上取决于每个向量具有的特定值组合。
【讨论】:
我想我看到了问题所在。如果我没记错的话,IEEE 浮点标准允许实现保留任何大于所需最小值的随机精度,并且额外的精度不必是恒定的。您的 d1 和 d2 有时相等,有时不相等。 这就是我的想法。就像我的仿函数给出了不一致的排序语义,所以 std::sort 变得混乱或其他东西。即便如此,为什么问题只发生在优化上?为什么我的第二个仿函数工作正常? 别人已经问过了,我再重复一遍,请给一个完整的程序(尽可能小)给你带来麻烦。如果需要,硬代码输入。 看起来这个样本已经足够了。 "为什么只有优化才会出现问题?" -- 没有可靠的答案,但可能的答案是,有时值以额外的精度保存在寄存器中,有时值被四舍五入并存储到内存中。【参考方案12】:哇,没想到答案这么快,这么多……
使用 std::sort() 对指针的 std::vector 进行排序时发生错误
我提供了严格-弱排序函子。
但我知道我提供的函子是正确的,因为我用过很多次而且效果很好。
另外,错误不能是向量中的一些无效指针,因为错误发生在我对向量进行排序时。如果我在不先应用 std::sort 的情况下遍历向量,则程序可以正常工作。
我刚刚使用 GDB 试图找出发生了什么。当 std::sort 调用我的仿函数时发生错误。显然 std::sort 正在将无效指针传递给我的仿函数。 (当然,这仅发生在优化版本中,任何级别的优化 -O、-O2、-O3)
【讨论】:
你能提供最小的、完整的、有这个问题的程序吗? 我也想看看。这里有些奇怪,因为 std::sort 的实现应该独立于 -O 级别。【参考方案13】:正如其他人指出的那样,可能是严格的别名。 将其关闭 o3 并重试。我的猜测是你在你的仿函数中做了一些指针技巧(作为int比较的快速浮点数?低2位的对象类型?)在内联模板函数中失败。 警告无助于捕捉这种情况。 “如果编译器可以检测到所有严格的别名问题,它也可以避免它们”仅仅更改不相关的代码行可能会使问题出现或消失,因为它会更改寄存器分配。
【讨论】:
【参考方案14】:更新后的问题将显示 ;) ,std::vector<T*>
存在问题。向量的一个常见错误是reserve()ing 应该是resize()d。结果,您将在数组范围之外编写。优化器可能会丢弃这些写入。
【讨论】:
【参考方案15】:在远处发布代码!它可能会做一些指针魔术,请参阅我以前的帖子。进行中间分配只是通过更改寄存器分配来隐藏代码中的错误。更能说明这一点的是输出改变了事情!
【讨论】:
【参考方案16】:真正的答案隐藏在这个线程中所有 cmets 的某个地方。首先:这不是编译器的错误。
问题与浮点精度有关。 distanceToPointSort
应该是一个函数,对于参数 (a,b) 和 (b,a) 都不应返回 true,但这正是编译器决定对某些数据路径使用更高精度时可能发生的情况。该问题尤其可能出现在但不限于没有-mfpmath=sse
的 x86 上。如果比较器的行为如此,sort
函数可能会变得混乱,分段错误也就不足为奇了。
我认为-ffloat-store
是这里的最佳解决方案(CesarB 已经建议)。
【讨论】:
以上是关于GCC:程序不适用于编译选项 -O3的主要内容,如果未能解决你的问题,请参考以下文章
std::stoi 的问题,不适用于 MinGW GCC 4.7.2
getExpoPushTokenAsync 不适用于独立应用程序