未显式调用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
相关产品推荐
相关产品推荐

