You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

未显式调用memcpy却在符号表中出现未定义的memcpy符号的原因排查

未显式调用memcpy却在符号表中出现未定义的memcpy符号的原因排查

从你给出的最小可复现例子(MRE)和工具输出来看,这个问题的核心是大结构体返回时的编译器隐式优化——你没有手动调用memcpy,但GCC为了处理大结构体的返回值传递,自动生成了调用memcpy的代码,具体拆解如下:

1. 大结构体的返回值传递逻辑

C语言的函数返回值如果是小类型(比如int、char,或者很小的结构体),编译器会直接用寄存器返回;但如果返回的是大结构体,寄存器放不下完整内容,就会触发「隐藏指针」的调用约定:

  • 调用函数的代码会预先在自己的栈上为返回的结构体分配一块内存
  • 调用函数时,会把这块内存的地址作为隐式第一个参数通过rdi寄存器传给被调用函数
  • 被调用函数内部先在自身栈上创建临时结构体(比如你代码里的res),初始化完成后,需要把临时结构体的内容拷贝到调用方提供的内存区域——这个拷贝操作就由编译器自动生成的memcpy完成

你的t_history结构体大小相当大:它包含1024个t_ent元素,总大小计算下来是1024 * (sizeof(t_ent_type) + sizeof(union u_ent) + sizeof(int)) = 1024 * 12 = 12288字节,完全无法通过寄存器返回,所以触发了这个优化。

2. 汇编代码中的实锤证据

看你提供的objdump反汇编结果,这段代码就是编译器生成的memcpy调用:

50: 48 8b 85 e8 cf ff ff mov -0x3018(%rbp),%rax  // 取出调用方传入的目标缓冲区地址
57: 48 89 c1 mov %rax,%rcx
5a: 48 8d 85 f0 cf ff ff lea -0x3010(%rbp),%rax  // 取函数内部临时变量res的地址(源地址)
61: ba 00 30 00 00 mov $0x3000,%edx              // 要拷贝的字节数:0x3000 = 12288,正好是t_history的总大小
66: 48 89 c6 mov %rax,%rsi                       // memcpy的第二个参数:源指针
69: 48 89 cf mov %rcx,%rdi                       // memcpy的第一个参数:目标指针
6c: e8 00 00 00 00 call 71 <init_history+0x71>   // 调用memcpy(此时符号未解析,所以nm显示U memcpy)

这段逻辑完全对应memcpy(目标缓冲区, res, sizeof(t_history)),这就是nm输出中出现U memcpy的直接原因。

3. 额外补充:关于__stack_chk_fail

这个符号和memcpy无关,它是GCC默认开启的-fstack-protector栈溢出检测机制生成的,函数返回时会检查栈上的保护标记是否被篡改,用于防御栈溢出攻击。

验证小技巧

如果你把HISTORY_SIZE改成很小的值(比如1),让t_history的大小足够小,编译器就会直接用寄存器或栈上直接操作返回结果,不会生成memcpy调用,此时再运行nm就不会看到U memcpy了。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 09:33:05