我正在编写一个包含4个嵌套for循环的算法。问题是在每一层都有一个指针被更新。最内层的循环只使用了其中的一个指针。该算法进行了一个复杂的计数。当我包含一个调试语句来记录索引的组合和计数的结果时,我得到了正确的答案。当省略调试语句时,计数不正确。2程序是用gcc的-O3选项编译的。3为什么会发生这种情况?
oipij1gg1#
在指责优化器之前,总是让你的代码经历一些类似valgrind,Purify等的事情,特别是当指责与指针相关的事情时。这并不是说优化器没有问题,但更有可能的是,问题出在您身上。我曾在各种C++编译器上工作过,也见过一些只在优化代码中发生的seg错误。人们经常会在为字符串分配空间时忘记计算\0,等等。当程序以不同的-O设置运行时,您会被分配到哪些页面上,这只是运气问题。另外,重要问题:你到底有没有处理受限指针?
gg58donl2#
打印出编译器生成的经过优化的汇编代码。与没有经过优化的汇编语言代码列表进行比较。编译器可能已经计算出一些变量可以被删除。它们没有在计算中使用。你可以试着和编译器斗智斗勇,把没有使用的变量因子化。编译器可能已将for循环替换为公式。在某些情况下(删除未使用的变量后),循环可以替换为简单的公式。例如,将变量加1的循环可以替换为乘法语句。你可以通过声明一个变量为volatile来告诉编译器让它成为volatile。volatile关键字告诉编译器这个变量的值可能会被程序外部的方法改变,编译器不应该缓存或删除这个变量。这是嵌入式系统编程中一个流行的技术。
for
volatile
vatpfxk53#
很可能你的程序不知何故利用了未定义的行为,这些行为在没有优化的情况下对你有利,但在-O3优化的情况下却对你不利。我在我的一个项目中有过类似的经历--它在-O2中工作得很好,但在-O3中就坏了。我在代码中大量使用setjmp()/longjmp(),我不得不使用一半的变量volatile来让它工作,所以我认为-O2已经足够好了。
-O3
-O2
setjmp()
longjmp()
0g0grzrc4#
听起来像是有东西在访问不应该访问的内存。调试符号以推迟坏消息而闻名。它是纯C语言还是像内联汇编这样的疯狂的东西?但是,在valgrind上运行它来检查是否会发生这种情况。另外,你是否尝试过用不同的优化级别编译?是否没有调试和优化?
vlju58qv5#
如果没有代码,这是很困难的,但这里有一些我以前见过的东西。调试print语句通常是编译器知道的值的唯一使用者。没有print语句,编译器认为它可以消除计算或存储该值所需的任何操作和内存需求。当print语句的参数列表中包含副作用时,也会发生类似的情况。
printf("%i %i\n", x, y = x - z);
另一种类型的错误可能是:
for( i = 0; i < END; i++) { int *a = &i; foo(a); } if (bar) { int * a; baz(a); }
这段代码可能会得到预期的结果,因为编译器可能会选择将两个a变量都存储在同一个位置,这样第二个a就会拥有另一个a的最后一个值。内联函数可能会有一些奇怪的行为,或者你在某种程度上依赖于它们没有被内联(或者有时相反),这经常是未经优化的代码的情况。你一定要试着把警告调到最大(-Wall代表gcc),这通常会告诉你代码的风险。
**(编辑)**刚想到另一个。
如果你有不止一种方法来引用一个变量,那么你可能会遇到这样的问题,即使没有优化也能正常工作,但是当优化打开时就会中断。第一个是一个值是否可以被信号处理程序或其他线程改变,你需要告诉编译器这一点,这样它就知道任何访问都假设这个值需要被重载和/或存储,这是通过使用volatile关键字来完成的。第二个是别名。这是当你创建两种不同的方式来访问同一个内存时。编译器通常会很快地假设你使用的是指针的别名,但并不总是这样。此外,它们是一些优化标志,告诉它们不要那么快地做出这些假设。以及可以愚弄编译器的方法(像while (foo != bar) { foo++; } *foo = x;这样的疯狂东西显然不是bar to foo的副本)。
while (foo != bar) { foo++; } *foo = x;
weylhg0b6#
虽然这个问题已经有10年了,但我还是偶然发现了一个类似的问题,我和一个朋友都没能解决这个问题。(也可以在GCC上使用-Wall -Wextra!),它没有任何与损坏的指针有关的事情要做。然后它就工作了,没有任何问题。这个故事的寓意是什么?* 总是确保你没有任何警告,不管是什么类型的!* 虽然这可能是为时已晚的OP看到,它可能至少帮助别人以后发现自己有类似的问题,像我。
-Wall -Wextra
6条答案
按热度按时间oipij1gg1#
在指责优化器之前,总是让你的代码经历一些类似valgrind,Purify等的事情,特别是当指责与指针相关的事情时。
这并不是说优化器没有问题,但更有可能的是,问题出在您身上。我曾在各种C++编译器上工作过,也见过一些只在优化代码中发生的seg错误。人们经常会在为字符串分配空间时忘记计算\0,等等。当程序以不同的-O设置运行时,您会被分配到哪些页面上,这只是运气问题。
另外,重要问题:你到底有没有处理受限指针?
gg58donl2#
打印出编译器生成的经过优化的汇编代码。与没有经过优化的汇编语言代码列表进行比较。
编译器可能已经计算出一些变量可以被删除。它们没有在计算中使用。你可以试着和编译器斗智斗勇,把没有使用的变量因子化。
编译器可能已将
for
循环替换为公式。在某些情况下(删除未使用的变量后),循环可以替换为简单的公式。例如,将变量加1的循环可以替换为乘法语句。你可以通过声明一个变量为
volatile
来告诉编译器让它成为volatile
。volatile
关键字告诉编译器这个变量的值可能会被程序外部的方法改变,编译器不应该缓存或删除这个变量。这是嵌入式系统编程中一个流行的技术。vatpfxk53#
很可能你的程序不知何故利用了未定义的行为,这些行为在没有优化的情况下对你有利,但在
-O3
优化的情况下却对你不利。我在我的一个项目中有过类似的经历--它在
-O2
中工作得很好,但在-O3
中就坏了。我在代码中大量使用setjmp()
/longjmp()
,我不得不使用一半的变量volatile
来让它工作,所以我认为-O2
已经足够好了。0g0grzrc4#
听起来像是有东西在访问不应该访问的内存。调试符号以推迟坏消息而闻名。
它是纯C语言还是像内联汇编这样的疯狂的东西?
但是,在valgrind上运行它来检查是否会发生这种情况。另外,你是否尝试过用不同的优化级别编译?是否没有调试和优化?
vlju58qv5#
如果没有代码,这是很困难的,但这里有一些我以前见过的东西。
调试print语句通常是编译器知道的值的唯一使用者。没有print语句,编译器认为它可以消除计算或存储该值所需的任何操作和内存需求。
当print语句的参数列表中包含副作用时,也会发生类似的情况。
另一种类型的错误可能是:
这段代码可能会得到预期的结果,因为编译器可能会选择将两个a变量都存储在同一个位置,这样第二个a就会拥有另一个a的最后一个值。
内联函数可能会有一些奇怪的行为,或者你在某种程度上依赖于它们没有被内联(或者有时相反),这经常是未经优化的代码的情况。
你一定要试着把警告调到最大(-Wall代表gcc),这通常会告诉你代码的风险。
**(编辑)**刚想到另一个。
如果你有不止一种方法来引用一个变量,那么你可能会遇到这样的问题,即使没有优化也能正常工作,但是当优化打开时就会中断。
第一个是一个值是否可以被信号处理程序或其他线程改变,你需要告诉编译器这一点,这样它就知道任何访问都假设这个值需要被重载和/或存储,这是通过使用volatile关键字来完成的。
第二个是别名。这是当你创建两种不同的方式来访问同一个内存时。编译器通常会很快地假设你使用的是指针的别名,但并不总是这样。此外,它们是一些优化标志,告诉它们不要那么快地做出这些假设。以及可以愚弄编译器的方法(像
while (foo != bar) { foo++; } *foo = x;
这样的疯狂东西显然不是bar to foo的副本)。weylhg0b6#
虽然这个问题已经有10年了,但我还是偶然发现了一个类似的问题,我和一个朋友都没能解决这个问题。(也可以在GCC上使用
-Wall -Wextra
!),它没有任何与损坏的指针有关的事情要做。然后它就工作了,没有任何问题。这个故事的寓意是什么?* 总是确保你没有任何警告,不管是什么类型的!* 虽然这可能是为时已晚的OP看到,它可能至少帮助别人以后发现自己有类似的问题,像我。