Coredump常见问题收集
发布时间:2025-11-11 10:12:24.504 文章来源:AiSoftCloud 浏览次数:52 下载次数:1 

问题案例

-O2编译选项

测试程序

  1. #include <iostream>
  2. bool func1() {
  3. std::cout << "test func 1" << std::endl;
  4. }
  5. bool func2() {
  6. std::cout << "test func 2" << std::endl;
  7. }
  8. int32_t main(int32_t argc, char** argv) {
  9. func1();
  10. func2();
  11. return 0;
  12. }

测试

1、使用正常的编译命令编译:

  1. g++ -o main1 main1.cc

运行:

  1. ./main1

输出结果正常:

  1. test func 1
  2. test func 2

2、使用-O2的编译命令编译:

  1. g++ -o main1 main1.cc -O2

运行:

  1. ./main1

输出结果崩溃:

  1. test func 1
  2. test func 2
  3. 段错误

使用gdb调试一下:

  1. sudo gdb ./main1

结果:

  1. [Thread debugging using libthread_db enabled]
  2. Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
  3. test func 1
  4. test func 2
  5. Program received signal SIGSEGV, Segmentation fault.
  6. 0x0000555555558040 in std::cout@GLIBCXX_3.4 ()
  7. (gdb) bt
  8. #0 0x0000555555558040 in std::cout@GLIBCXX_3.4 ()
  9. #1 0x0000555555555120 in std::ios_base::Init::Init()@plt ()
  10. #2 0x000055555555512f in main ()
  11. (gdb)

可以看到顶层堆栈是在std::cout,看看不出来原因,加上调试信息重新编译:

  1. g++ -o main1 main1.cc -O2 -g

重新调试:

  1. sudo gdb ./main1

结果:

  1. [Thread debugging using libthread_db enabled]
  2. Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
  3. test func 1
  4. test func 2
  5. Program received signal SIGSEGV, Segmentation fault.
  6. 0x0000555555558040 in std::cout@GLIBCXX_3.4 ()
  7. (gdb) bt
  8. #0 0x0000555555558040 in std::cout@GLIBCXX_3.4 ()
  9. #1 0x0000555555555120 in std::ios_base::Init::Init()@plt ()
  10. #2 0x000055555555512f in main (argc=<optimized out>, argv=<optimized out>) at main1.cc:12
  11. (gdb)

可以看出问题在main1.cc的第12行,就是调用func1()的这一行。

加上-fsanitize=address编译选项调试一下:
编译命令:

  1. g++ -o main1 main1.cc -g -O2 -fsanitize=address

运行./mian1输出结果:

  1. test func 1
  2. test func 2
  3. AddressSanitizer:DEADLYSIGNAL
  4. =================================================================
  5. ==420497==ERROR: AddressSanitizer: SEGV on unknown address 0x557cf3b4f100 (pc 0x557cf3b4f100 bp 0x000000000001 sp 0x7ffcf3748200 T0)
  6. ==420497==The signal is caused by a READ memory access.
  7. ==420497==Hint: PC is at a non-executable region. Maybe a wild jump?
  8. #0 0x557cf3b4f100 in _ZSt4cout@GLIBCXX_3.4 (/home/ubuntu/kai/xhumanoid/test/1/main1+0x4100)
  9. AddressSanitizer can not provide additional info.
  10. SUMMARY: AddressSanitizer: SEGV (/home/ubuntu/kai/xhumanoid/test/1/main1+0x4100) in _ZSt4cout@GLIBCXX_3.4
  11. ==420497==ABORTING

问题原因

问题原因是因为func1()函数本身存在返回类型bool,但是没有写返回值对应的代码(return true;或return false;),加上之后问题消失:

  1. #include <iostream>
  2. bool func1() {
  3. std::cout << "test func 1" << std::endl;
  4. return true; // 此处需要加上返回值
  5. }
  6. bool func2() {
  7. std::cout << "test func 2" << std::endl;
  8. return true; // 此处需要加上返回值
  9. }
  10. int32_t main(int32_t argc, char** argv) {
  11. func1();
  12. func2();
  13. return 0;
  14. }

加上后,重新测试正常。

根因分析

该问题的根因为代码中的未定义行为,在使用-O2会因优化暴露问题,表现为返回值异常,这并非优化本身改变了返回值,而是未优化时的 “侥幸正确” 被修正。

查看汇编代码:
正常编译出的汇编代码(部分),编译命令g++ -S -o main1.s main1.cc

  1. _Z5func1v:
  2. .LFB1731:
  3. .cfi_startproc
  4. endbr64
  5. pushq %rbp ; rbp寄存器的值压栈,保存上一个栈帧的基地址
  6. .cfi_def_cfa_offset 16 ; CFI(调用帧信息)指令,调整栈帧偏移为16字节(64位环境)
  7. .cfi_offset 6, -16 ; 记录rbp寄存器(编号6)在栈中的偏移为-16字节(用于异常处理时恢复栈帧)
  8. movq %rsp, %rbp ; rsp(栈顶指针)的值赋给rbp,建立当前函数的栈帧基地址
  9. .cfi_def_cfa_register 6 ; CFI指令,指定rbp为当前栈帧的基地址寄存器
  10. leaq .LC0(%rip), %rax ; 加载字符串常量.LC0的地址到raxRIP相对寻址,.LC0是字符串在数据段的标签)
  11. movq %rax, %rsi ; 将字符串地址存入rsi(函数调用的第二个参数寄存器,对应cout的输出内容)
  12. leaq _ZSt4cout(%rip), %rax ; 加载cout对象的地址到rax_ZSt4coutcout的符号名,C++名字修饰后)
  13. movq %rax, %rdi ; cout地址存入rdi(函数调用的第一个参数寄存器,对应输出流对象)
  14. call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
  15. ; 调用operator<<(cout, 字符串)函数(PLT是延迟绑定表,处理动态链接),输出字符串
  16. movq _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_@GOTPCREL(%rip), %rdx
  17. ; 加载endl的地址到rdxendl是一个操纵符,本质是函数指针)
  18. movq %rdx, %rsi ; endl地址存入rsi(第二个参数,传给operator<<)
  19. movq %rax, %rdi ; 将上一步cout的返回值(仍是cout)存入rdi(第一个参数)
  20. call _ZNSolsEPFRSoS_E@PLT ; 调用operator<<(cout, endl)函数,输出换行并刷新缓冲区
  21. nop ; 空操作(可能用于对齐或优化)
  22. popq %rbp ; 从栈中恢复rbp的值,销毁当前栈帧
  23. .cfi_def_cfa 7, 8 ; CFI指令,恢复栈帧为rsp(寄存器7),偏移8字节
  24. ret ; 函数返回,从栈中弹出返回地址并跳转
  25. .cfi_endproc

加上-O2编译出的汇编代码(部分),编译命令g++ -S -o main2.s main1.cc -O2

  1. _Z5func1v:
  2. .LFB1812:
  3. .cfi_startproc
  4. endbr64
  5. pushq %rax ; rax寄存器的值压栈(64位,占8字节),临时调整栈顶
  6. .cfi_def_cfa_offset 16 ; CFI指令:调整当前栈帧的CFA(调用帧地址)偏移为16字节(初始栈+8字节push
  7. popq %rax ; 从栈中弹出值恢复到rax,栈顶回到原位置
  8. .cfi_def_cfa_offset 8 ; CFI指令:恢复CFA偏移为8字节(栈顶回到初始状态)
  9. leaq _ZSt4cout(%rip), %rdi ; 加载`std::cout`的地址到rdi(第一个参数寄存器,对应输出流对象)
  10. leaq .LC0(%rip), %rsi ; 加载字符串常量.LC0的地址到rsi(第二个参数寄存器,对应输出的字符串)
  11. subq $8, %rsp ; 栈顶指针rsp减去8字节,为函数调用预留8字节栈空间(可能用于对齐或临时存储)
  12. .cfi_def_cfa_offset 16 ; CFI指令:调整CFA偏移为16字节(原8字节+8字节subq
  13. call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc@PLT
  14. ; 调用`operator<<(cout, 字符串)`函数(PLT动态链接),输出字符串,返回值(cout的引用)存于rax
  15. movq %rax, %rdi ; 将上一步返回的cout引用存入rdi(作为下一个函数的第一个参数)
  16. call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_.isra.0
  17. ; 调用`endl`操纵符函数(`.isra.0`表示编译器内部的同构化优化,不影响功能),输出换行并刷新缓冲区
  18. .cfi_endproc

上述两段汇编代码差异:

  • 1、栈帧处理:前一段用rbp建立完整栈帧(传统栈帧方式),这段则直接操作rsp(精简栈帧,编译器优化结果)。
  • 2、endl调用方式:前一段通过operator<<间接调用endl,这段直接调用endl的优化版本(.isra.0是编译器内部标识,用于函数复用优化)。
  • 3、临时栈操作:增加了pushq %rax/popq %rax的临时调整,可能是为了满足特定平台的栈对齐要求。

何时会省略 %rbp?

  • 1、现代编译器(如 GCC、Clang)在开启优化(如 -O2)时,可能会省略 %rbp 的使用(称为 “帧指针省略”,Frame Pointer Omission),直接通过 %rsp 访问栈帧内容。这样可以:
  • 2、减少栈帧初始化的指令(省去 pushq %rbp; movq %rsp, %rbp)。
  • 3、释放 %rbp 作为通用寄存器使用,提高效率。
  • 4、此时栈帧的访问完全依赖 %rsp 的偏移(如 -16(%rsp)),但逻辑上仍保持栈的结构。

-O2编译选项对于函数返回值的优化,表现在:

  • 1、-O2优化级别不会改变函数返回值的语义正确性,核心是通过调整执行逻辑提升效率,不影响最终返回结果。
  • 2、-O2 优化不会修改函数 “该返回什么”,只会优化 “如何返回”,最终返回值与未优化(-O0)时一致(前提是代码无未定义行为)。

常见优化方式(不影响返回值):

  • 1、直接返回计算结果,而非存储到局部变量再读取,减少内存访问。
  • 2、利用寄存器传递返回值,替代栈内存,提升速度(符合调用约定)。
  • 3、消除冗余计算,比如编译期计算常量表达式后直接返回结果。
  • 4、合并或简化返回路径,多个 return 语句若结果一致会被合并,避免重复逻辑。

总结

在 C/C++ 中,当函数声明了返回类型(非 void)但未写返回值时,编译器通常会给出警告(如 warning: control reaches end of non-void function),但仍会生成可执行文件。此时若启用 -O2 等优化选项,程序可能崩溃,核心原因是未定义行为(Undefined Behavior) 在优化下的暴露。

具体原因分析:
未定义行为的本质

  • 1、C/C++ 标准规定:非 void 函数必须返回对应类型的值,否则属于未定义行为。编译器对此不做强制检查,仅可能警告,但生成的代码行为不可预测。
  • 2、优化选项(-O2)的影响
    • 关闭优化(如 -O0)时,编译器可能会 “侥幸” 让程序运行(例如,返回寄存器中残留的随机值),但这只是巧合。
    • 启用 -O2 后,编译器会进行更激进的代码优化(如流程分析、寄存器复用、删除 “冗余” 代码):
      • 若编译器推断函数 “必然返回值”(根据声明),可能会优化掉对 “未返回” 路径的检查,直接使用返回值。
      • 当实际未返回时,程序可能访问非法内存、破坏栈平衡,或因依赖不存在的返回值而崩溃(如 segfault)。

结论
非 void 函数必须显式返回对应类型的值,否则即使在低优化下能运行,也属于错误代码。-O2 等优化只是让未定义行为以更明显的方式暴露(崩溃),而非优化本身导致问题。解决方法是补全返回语句,消除编译器警告。

更多文章可关注公众号
aisoftcloud