【发布时间】:2020-10-30 00:48:29
【问题描述】:
这个简单的c:
#include <stdio.h>
#include <string.h>
int *add(int a, int b){
int ar[1];
int result = a+b;
memcpy(ar, &result, sizeof(int));
return ar;
}
int main(){
int a = add(1,2)[0];
printf("%i\n",a);
}
编译成这样:
.text
.globl add
.type add, @function
add:
pushq %rbp #
movq %rsp, %rbp #,
movl %edi, -20(%rbp) # a, a
movl %esi, -24(%rbp) # b, b
# a.c:5: int result = a+b;
movl -20(%rbp), %edx # a, tmp91
movl -24(%rbp), %eax # b, tmp92
addl %edx, %eax # tmp91, _1
# a.c:5: int result = a+b;
movl %eax, -8(%rbp) # _1, result
# a.c:6: memcpy(ar, &result, sizeof(int)); ---I SEE NO CALL INSTRUCTION---
movl -8(%rbp), %eax # MEM[(char * {ref-all})&result], _6
movl %eax, -4(%rbp) # _6, MEM[(char * {ref-all})&ar]
# a.c:7: return ar;
movl $0, %eax #--THE FUNCTION SHOULD RETURN ADDRESS OF ARRAY, NOT 0. OTHERWISE command terminated
# lea -4(%rbp), %rax #--ONLY THIS IS CORRECT, NOT `0`
# a.c:8: }
popq %rbp #
ret
.size add, .-add
.section .rodata
.LC0:
.string "%i\n"
.text
.globl main
.type main, @function
main:
pushq %rbp #
movq %rsp, %rbp #,
subq $16, %rsp #,
# a.c:11: int a = add(1,2)[0];
movl $2, %esi #,
movl $1, %edi #,
call add #
# a.c:11: int a = add(1,2)[0];
movl (%rax), %eax # *_1, tmp90
movl %eax, -4(%rbp) # tmp90, a
# a.c:12: printf("%i\n",a);
movl -4(%rbp), %eax # a, tmp91
movl %eax, %esi # tmp91,
leaq .LC0(%rip), %rdi #,
movl $0, %eax #,
call printf@PLT #
movl $0, %eax #, _6
# a.c:13: }
leave
ret
.size main, .-main
.ident "GCC: (Debian 8.3.0-6) 8.3.0"
.section .note.GNU-stack,"",@progbits
stdlib 中的每个函数,如 printf 或 puts 都是来自 GOT 的 call(即 %rip 寄存器保存 GOT 的地址)。但不是memcpy,它就像“汇编内联指令”而不是常规的调用地址。那么memcpy 甚至是一个符号吗?如果是这样,为什么它不作为call 的论据? memcpy 在 GOT 表中吗?如果是这样,从 GOT 到该符号的偏移量是多少?
【问题讨论】:
-
编译器可以理解
memcpy的功能,并且(对于简单的调用)只需编写汇编程序。 -
你能把
memcpy的地址拿下来吗?是的。它必须是带有导出符号的真实函数。编译器是否强制使用它?不,如果它可能更短或更快,它可以直接内联。这仅适用于众所周知的标准函数。 -
顺便说一下,从
add返回的是一个非静态局部变量的地址,从add返回后解引用会调用未定义的行为。 -
@milanHrabos 允许编译器对具有正式未定义行为的代码执行它想要的任何操作。你很幸运,它没有生成让恶魔从你鼻子里飞出来的汇编语言。
-
我并不同意这一点。但无论如何,现在的标准就是这样。您可能对 John Regehr 的“Friendly C”项目感兴趣。 (但请注意,如果我正在重写标准,我会使这个特定场景成为一个硬编译时错误。)