#include <iostream>bool func1() {std::cout << "test func 1" << std::endl;}bool func2() {std::cout << "test func 2" << std::endl;}int32_t main(int32_t argc, char** argv) {func1();func2();return 0;}
1、使用正常的编译命令编译:
g++ -o main1 main1.cc
运行:
./main1
输出结果正常:
test func 1test func 2
2、使用-O2的编译命令编译:
g++ -o main1 main1.cc -O2
运行:
./main1
输出结果崩溃:
test func 1test func 2段错误
使用gdb调试一下:
sudo gdb ./main1
结果:
[Thread debugging using libthread_db enabled]Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".test func 1test func 2Program received signal SIGSEGV, Segmentation fault.0x0000555555558040 in std::cout@GLIBCXX_3.4 ()(gdb) bt#0 0x0000555555558040 in std::cout@GLIBCXX_3.4 ()#1 0x0000555555555120 in std::ios_base::Init::Init()@plt ()#2 0x000055555555512f in main ()(gdb)
可以看到顶层堆栈是在std::cout,看看不出来原因,加上调试信息重新编译:
g++ -o main1 main1.cc -O2 -g
重新调试:
sudo gdb ./main1
结果:
[Thread debugging using libthread_db enabled]Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".test func 1test func 2Program received signal SIGSEGV, Segmentation fault.0x0000555555558040 in std::cout@GLIBCXX_3.4 ()(gdb) bt#0 0x0000555555558040 in std::cout@GLIBCXX_3.4 ()#1 0x0000555555555120 in std::ios_base::Init::Init()@plt ()#2 0x000055555555512f in main (argc=<optimized out>, argv=<optimized out>) at main1.cc:12(gdb)
可以看出问题在main1.cc的第12行,就是调用func1()的这一行。
加上-fsanitize=address编译选项调试一下:
编译命令:
g++ -o main1 main1.cc -g -O2 -fsanitize=address
运行./mian1输出结果:
test func 1test func 2AddressSanitizer:DEADLYSIGNAL===================================================================420497==ERROR: AddressSanitizer: SEGV on unknown address 0x557cf3b4f100 (pc 0x557cf3b4f100 bp 0x000000000001 sp 0x7ffcf3748200 T0)==420497==The signal is caused by a READ memory access.==420497==Hint: PC is at a non-executable region. Maybe a wild jump?#0 0x557cf3b4f100 in _ZSt4cout@GLIBCXX_3.4 (/home/ubuntu/kai/xhumanoid/test/1/main1+0x4100)AddressSanitizer can not provide additional info.SUMMARY: AddressSanitizer: SEGV (/home/ubuntu/kai/xhumanoid/test/1/main1+0x4100) in _ZSt4cout@GLIBCXX_3.4==420497==ABORTING
问题原因是因为func1()函数本身存在返回类型bool,但是没有写返回值对应的代码(return true;或return false;),加上之后问题消失:
#include <iostream>bool func1() {std::cout << "test func 1" << std::endl;return true; // 此处需要加上返回值}bool func2() {std::cout << "test func 2" << std::endl;return true; // 此处需要加上返回值}int32_t main(int32_t argc, char** argv) {func1();func2();return 0;}
加上后,重新测试正常。
该问题的根因为代码中的未定义行为,在使用-O2会因优化暴露问题,表现为返回值异常,这并非优化本身改变了返回值,而是未优化时的 “侥幸正确” 被修正。
查看汇编代码:
正常编译出的汇编代码(部分),编译命令g++ -S -o main1.s main1.cc:
_Z5func1v:.LFB1731:.cfi_startprocendbr64pushq %rbp ; 将rbp寄存器的值压栈,保存上一个栈帧的基地址.cfi_def_cfa_offset 16 ; CFI(调用帧信息)指令,调整栈帧偏移为16字节(64位环境).cfi_offset 6, -16 ; 记录rbp寄存器(编号6)在栈中的偏移为-16字节(用于异常处理时恢复栈帧)movq %rsp, %rbp ; 将rsp(栈顶指针)的值赋给rbp,建立当前函数的栈帧基地址.cfi_def_cfa_register 6 ; CFI指令,指定rbp为当前栈帧的基地址寄存器leaq .LC0(%rip), %rax ; 加载字符串常量.LC0的地址到rax(RIP相对寻址,.LC0是字符串在数据段的标签)movq %rax, %rsi ; 将字符串地址存入rsi(函数调用的第二个参数寄存器,对应cout的输出内容)leaq _ZSt4cout(%rip), %rax ; 加载cout对象的地址到rax(_ZSt4cout是cout的符号名,C++名字修饰后)movq %rax, %rdi ; 将cout地址存入rdi(函数调用的第一个参数寄存器,对应输出流对象)call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT; 调用operator<<(cout, 字符串)函数(PLT是延迟绑定表,处理动态链接),输出字符串movq _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx; 加载endl的地址到rdx(endl是一个操纵符,本质是函数指针)movq %rdx, %rsi ; 将endl地址存入rsi(第二个参数,传给operator<<)movq %rax, %rdi ; 将上一步cout的返回值(仍是cout)存入rdi(第一个参数)call _ZNSolsEPFRSoS_E@PLT ; 调用operator<<(cout, endl)函数,输出换行并刷新缓冲区nop ; 空操作(可能用于对齐或优化)popq %rbp ; 从栈中恢复rbp的值,销毁当前栈帧.cfi_def_cfa 7, 8 ; CFI指令,恢复栈帧为rsp(寄存器7),偏移8字节ret ; 函数返回,从栈中弹出返回地址并跳转.cfi_endproc
加上-O2编译出的汇编代码(部分),编译命令g++ -S -o main2.s main1.cc -O2:
_Z5func1v:.LFB1812:.cfi_startprocendbr64pushq %rax ; 将rax寄存器的值压栈(64位,占8字节),临时调整栈顶.cfi_def_cfa_offset 16 ; CFI指令:调整当前栈帧的CFA(调用帧地址)偏移为16字节(初始栈+8字节push)popq %rax ; 从栈中弹出值恢复到rax,栈顶回到原位置.cfi_def_cfa_offset 8 ; CFI指令:恢复CFA偏移为8字节(栈顶回到初始状态)leaq _ZSt4cout(%rip), %rdi ; 加载`std::cout`的地址到rdi(第一个参数寄存器,对应输出流对象)leaq .LC0(%rip), %rsi ; 加载字符串常量.LC0的地址到rsi(第二个参数寄存器,对应输出的字符串)subq $8, %rsp ; 栈顶指针rsp减去8字节,为函数调用预留8字节栈空间(可能用于对齐或临时存储).cfi_def_cfa_offset 16 ; CFI指令:调整CFA偏移为16字节(原8字节+8字节subq)call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT; 调用`operator<<(cout, 字符串)`函数(PLT动态链接),输出字符串,返回值(cout的引用)存于raxmovq %rax, %rdi ; 将上一步返回的cout引用存入rdi(作为下一个函数的第一个参数)call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_.isra.0; 调用`endl`操纵符函数(`.isra.0`表示编译器内部的同构化优化,不影响功能),输出换行并刷新缓冲区.cfi_endproc
上述两段汇编代码差异:
何时会省略 %rbp?
-O2编译选项对于函数返回值的优化,表现在:
常见优化方式(不影响返回值):
在 C/C++ 中,当函数声明了返回类型(非 void)但未写返回值时,编译器通常会给出警告(如 warning: control reaches end of non-void function),但仍会生成可执行文件。此时若启用 -O2 等优化选项,程序可能崩溃,核心原因是未定义行为(Undefined Behavior) 在优化下的暴露。
具体原因分析:
未定义行为的本质
结论
非 void 函数必须显式返回对应类型的值,否则即使在低优化下能运行,也属于错误代码。-O2 等优化只是让未定义行为以更明显的方式暴露(崩溃),而非优化本身导致问题。解决方法是补全返回语句,消除编译器警告。