10

我正在编写一个 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 ;
    }
} ;

不幸的是,这个问题很难重现,因为它发生在一些特定的值上。在对一千多个向量中的一个进行排序时,我得到了分段错误,因此它实际上取决于每个向量具有的值的特定组合。

4

16 回答 16

8

我会假设你的代码首先是错误的。
虽然很难说。

您的代码编译时是否出现 0 个警告?

 g++ -Wall -Wextra -pedantic -ansi
于 2008-11-11T05:17:12.233 回答
8

既然您发布了代码片段并找到了可行的解决方法(@Windows 程序员的回答),我可以说您正在寻找的可能是-ffloat-store.

-float-存储

不要将浮点变量存储在寄存器中,并禁止其他可能改变浮点值是从寄存器还是内存中获取的选项。

此选项可防止在诸如 68000 之类的机器上出现不希望出现的过高精度,其中浮动寄存器(68881 的)保持比双精度应有的精度更高。对于 x86 架构也是如此。对于大多数程序来说,超额精度只会带来好处,但少数程序依赖于 IEEE 浮点的精确定义。在修改它们以将所有相关的中间计算存储到变量中之后,对此类程序使用 -ffloat-store。

来源: http: //gcc.gnu.org/onlinedocs/gcc-3.4.6/gcc/Optimize-Options.html

于 2008-11-12T13:35:09.013 回答
7

这是一些似乎有效的代码,直到您点击 -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 是相当安全的,并且只会将我们带到下一个内存位置。在上面的代码中,当我们对内存包含的内容做出假设时,就会出现问题,标准当然不会接近。

于 2008-11-11T07:14:53.027 回答
5

错误在您的代码中。根据 C 标准,您可能正在做一些调用未定义行为的事情,而这种行为恰好可以在没有优化的情况下工作,但是当 GCC 为执行其优化做出某些假设时,当这些假设不正确时,代码就会中断。确保使用该-Wall选项进行编译,这-Wextra也可能是一个好主意,并查看您是否收到任何警告。您也可以尝试-ansior -pedantic,但这些可能会导致误报。

于 2008-11-11T05:16:42.860 回答
5

作为一个实验,试着看看这是否会迫使编译器一致地四舍五入。

volatile float d1=distance(point,p1) ;
volatile float d2=distance(point,p2) ;
return d1 < d2 ;
于 2008-11-11T23:30:11.903 回答
4

您可能会遇到混叠问题(或者可能是其他一百万个问题)。查找 -fstrict-aliasing 选项。

如果没有更多信息,这种问题是不可能正确回答的。

于 2008-11-11T05:16:33.750 回答
3

这很少是编译器的错误,但编译器确实存在错误,并且它们通常在不同的优化级别上表现出来(例如,如果在优化过程中存在错误)。

通常在报告编程问题时:提供一个最小的代码示例来演示问题,这样人们就可以将代码保存到文件中,编译并运行它。尽可能简单地重现您的问题。

此外,尝试不同版本的 GCC(编译自己的 GCC 非常容易,尤其是在 Linux 上)。如果可能,请尝试使用其他编译器。英特尔 C 有一个或多或少兼容 GCC 的编译器(我认为,非商业用途免费)。这将有助于查明问题。

于 2008-11-11T07:05:13.297 回答
2

它几乎(几乎)从来不是编译器。

首先,确保您使用 -Wall 进行无警告编译。

如果这没有给你一个“灵光乍现”的时刻,请将调试器附加到崩溃的可执行文件的优化程度最低的版本,并查看它在做什么以及它去了哪里。

5 会让你得到 10,你已经解决了这个问题。

于 2008-11-11T05:21:46.577 回答
2

几天前遇到了同样的问题,就我而言,它是别名。与其他编译器相比,GCC 的做法有所不同,但并没有错。GCC 已成为某些人可能称之为 C++ 标准的规则律师,并且它们的实现是正确的,但你也必须在 C++ 中真正正确,否则它会过度优化某些东西,这很痛苦。但是你得到了速度,所以不能抱怨。

于 2008-11-11T05:27:40.793 回答
1

在阅读了一些评论后,我希望在这里得到一些反对意见,但在控制台游戏编程世界中,更高的优化级别有时会在奇怪的边缘情况下生成不正确的代码,这是相当普遍的知识。不过,很可能可以通过对代码进行细微的更改来修复边缘情况。

于 2008-11-11T06:36:28.893 回答
1

好吧...这是我遇到过的最奇怪的问题之一。
我认为我没有足够的证据来说明这是一个 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 ;
    }
} ;

不幸的是,这个问题很难重现,因为它发生在一些特定的值上。在对一千多个向量中的一个进行排序时,我得到了分段错误,因此它实际上取决于每个向量具有的值的特定组合。

于 2008-11-11T23:07:30.420 回答
0

哇,没想到答案这么快,这么多……

使用 std::sort() 对指针的 std::vector 进行排序时发生错误

我提供了严格弱序函子。

但是我知道我提供的函子是正确的,因为我已经使用了很多并且它工作正常。

另外,错误不能是向量中的一些无效指针,因为错误发生在我对向量进行排序时。如果我在不先应用 std::sort 的情况下遍历向量,则程序可以正常工作。

我只是使用 GDB 试图找出发生了什么。当 std::sort 调用我的仿函数时发生错误。显然 std::sort 正在将无效指针传递给我的仿函数。(当然,这仅发生在优化版本中,任何级别的优化 -O、-O2、-O3)

于 2008-11-11T05:45:46.297 回答
0

正如其他人指出的那样,可能是严格的混叠。将其关闭 o3 并重试。我的猜测是你在你的仿函数中做了一些指针技巧(作为int比较的快速浮点数?低2位的对象类型?)在内联模板函数中失败。警告无助于捕捉这种情况。“如果编译器可以检测到所有严格的别名问题,它也可以避免它们”只是更改不相关的代码行可能会使问题出现或消失,因为它会更改寄存器分配。

于 2008-11-11T07:28:31.453 回答
0

正如更新后的问题将显示的那样;),问题存在于std::vector<T*>. 向量的一个常见错误是reserve()ing 应该是resize()d。结果,您将在数组边界之外编写。优化器可能会丢弃这些写入。

于 2008-11-11T13:41:20.173 回答
0

将代码发布在远处!它可能会做一些指针魔术,请参阅我以前的帖子。进行中间分配只是通过更改寄存器分配来隐藏代码中的错误。更能说明这一点的是输出改变了事情!

于 2008-11-12T04:10:07.110 回答
0

真正的答案隐藏在这个线程的所有评论中。首先:这不是编译器中的错误。

问题与浮点精度有关。distanceToPointSort应该是一个函数,对于参数 (a,b) 和 (b,a) 都不应返回 true,但这正是编译器决定对某些数据路径使用更高精度时可能发生的情况。该问题尤其可能出现在但不限于没有-mfpmath=sse. 如果比较器以这种方式运行,则sort函数可能会变得混乱,分段错误也就不足为奇了。

我认为-ffloat-store这里的最佳解决方案(CesarB 已经建议)。

于 2009-09-15T09:00:39.893 回答