为何使用-O3编译时该C多线程程序无法终止?
C程序不同编译优化下行为差异的疑问解答
问题背景
以下C程序在不同GCC编译优化选项下表现出完全不同的行为:使用-O3编译时永不终止,使用-O0编译时会很快终止。
测试代码
#include <stdio.h> #include <pthread.h> void *f(void *v) { int *i = (int *)v; *i = 0; printf("set to 0!\n"); return NULL; } int main() { const int c = 1; int i = 0; pthread_t thread; void *ptr = (void *)&c; while (c) { i++; if (i == 1000) { pthread_create(&thread, NULL, &f, ptr); } } printf("done\n"); }
不同优化选项下的运行结果
username@hostname:/src$ gcc -O0 main.c -o main username@hostname:/src$ ./main done set to 0! set to 0! username@hostname:/src$ gcc -O3 main.c -o main username@hostname:/src$ ./main set to 0! set to 0! set to 0! set to 0! set to 0! set to 0! ^C username@hostname:/src$
教授课件指出,程序是否终止取决于GCC选项,-O3下不会终止的原因是变量c可能被编译器优化为寄存器缓存或直接替换为常量,导致线程间无法感知值的变化,解决方案是使用volatile关键字。
疑问与解答
1. 声明const volatile int c = 1;后程序在-O3下可终止,是否意味着线程都会从主存读写c?
可以这么理解,更准确的说法是:volatile关键字强制编译器放弃对c的读写优化——每次访问c都必须直接操作其对应的内存地址,不能把值缓存到寄存器里,也不能直接将c替换成常量值。这样主线程的while(c)每次都会去内存读取c的最新值,子线程修改内存中的c后,主线程就能立刻感知到变化,从而退出循环。
2. 修改const变量属于未定义行为,但测试程序中成功修改并输出了修改后的值,这是为何?
未定义行为不等于“一定会出错”,而是C标准不对这种行为的结果做任何保证。在这个测试中:
-O0无优化模式下,编译器仅在编译阶段做const的只读检查,但没有把c放到内存的只读段(比如.rodata)中,通过指针强制转换后,硬件层面允许修改该内存地址的值,所以看起来“成功”了。- 如果换成
-O3优化,编译器可能会把c放到只读内存,此时修改会触发段错误;或者直接把while(c)替换成while(1),根本不会读取内存中的c值。这种“成功”只是特定编译环境下的偶然结果,换编译器、系统或优化选项,结果可能完全不同。
3. 是否可理解为-O0下线程共享存储c的寄存器,而-O3下c存于非共享寄存器或while(c)被优化为while(1)?
这种理解存在误区,核心是两种优化模式的策略差异:
-O0模式下,编译器几乎不做优化,while(c)每次都会从内存读取c的值,子线程修改内存后,主线程下一次循环就能读到新值,所以会终止。寄存器是线程私有的,每个线程都有自己的寄存器组,不存在线程共享寄存器的情况。-O3模式下,编译器看到c被声明为const,会默认认为它的值永远不会改变,直接把while(c)优化成while(1)——完全跳过对c的内存读取操作,所以不管子线程怎么修改内存里的c,主线程都不会感知到,循环永远不会终止。
内容的提问来源于stack exchange,提问作者Ruibin Zhang
相关产品推荐
相关产品推荐

