memcpy 真的是带符号的函数吗?
Is memcpy a really function with symbol?
这个简单的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 [=11=], %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 , %rsp #,
# a.c:11: int a = add(1,2)[0];
movl , %esi #,
movl , %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 [=11=], %eax #,
call printf@PLT #
movl [=11=], %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 table 中吗?如果是这样,从 GOT 到该符号的偏移量是多少?
所以首先,你有一个错误:
$ cc -O2 -S test.c
test.c: In function ‘add’:
test.c:7:12: warning: function returns address of local variable
返回局部变量的地址具有未定义的行为,当且仅当调用者使用该值时;这就是为什么您的编译器生成了 returned 空指针的代码,如果使用它会使程序崩溃,但在其他情况下是无害的。事实上,我的 GCC 副本只为 add
:
生成这个
add:
xorl %eax, %eax
ret
因为 return 值的处理使得 add
中的其他操作成为死代码。
(“仅在使用时”限制也是我的编译器生成警告而不是硬错误的原因。)
现在,如果我修改您的程序使其具有明确定义的行为,例如
#include <stdio.h>
#include <string.h>
void add(int *sum, int a, int b)
{
int result = a+b;
memcpy(sum, &result, sizeof(int));
}
int main(void)
{
int a;
add(&a, 1, 2);
printf("%i\n",a);
return 0;
}
然后我确实看到了 memcpy
调用已被内联代码替换的汇编代码:
add:
addl %edx, %esi
movl %esi, (%rdi)
ret
这是许多现代 C 编译器的一个特性:它们知道某些 C 库函数的作用,并且可以在有意义时内联它们。 (您可以看到,在这种情况下,生成的代码比实际调用 memcpy
时生成的代码更小、更快。)
GCC 允许我使用命令行选项关闭此功能:
$ gcc -O2 -ffreestanding test.c
$ sed -ne '/^add:/,/cfi_endproc/{; /^\.LF[BE]/d; /\.cfi_/d; p; }' test.s
add:
subq , %rsp
addl %edx, %esi
movl , %edx
movl %esi, 12(%rsp)
leaq 12(%rsp), %rsi
call memcpy@PLT
addq , %rsp
ret
在此模式下,add
中对 memcpy
的调用与 main
中对 printf
的调用被视为相同。您的编译器可能有类似的选项。
这个简单的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 [=11=], %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 , %rsp #,
# a.c:11: int a = add(1,2)[0];
movl , %esi #,
movl , %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 [=11=], %eax #,
call printf@PLT #
movl [=11=], %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 table 中吗?如果是这样,从 GOT 到该符号的偏移量是多少?
所以首先,你有一个错误:
$ cc -O2 -S test.c
test.c: In function ‘add’:
test.c:7:12: warning: function returns address of local variable
返回局部变量的地址具有未定义的行为,当且仅当调用者使用该值时;这就是为什么您的编译器生成了 returned 空指针的代码,如果使用它会使程序崩溃,但在其他情况下是无害的。事实上,我的 GCC 副本只为 add
:
add:
xorl %eax, %eax
ret
因为 return 值的处理使得 add
中的其他操作成为死代码。
(“仅在使用时”限制也是我的编译器生成警告而不是硬错误的原因。)
现在,如果我修改您的程序使其具有明确定义的行为,例如
#include <stdio.h>
#include <string.h>
void add(int *sum, int a, int b)
{
int result = a+b;
memcpy(sum, &result, sizeof(int));
}
int main(void)
{
int a;
add(&a, 1, 2);
printf("%i\n",a);
return 0;
}
然后我确实看到了 memcpy
调用已被内联代码替换的汇编代码:
add:
addl %edx, %esi
movl %esi, (%rdi)
ret
这是许多现代 C 编译器的一个特性:它们知道某些 C 库函数的作用,并且可以在有意义时内联它们。 (您可以看到,在这种情况下,生成的代码比实际调用 memcpy
时生成的代码更小、更快。)
GCC 允许我使用命令行选项关闭此功能:
$ gcc -O2 -ffreestanding test.c
$ sed -ne '/^add:/,/cfi_endproc/{; /^\.LF[BE]/d; /\.cfi_/d; p; }' test.s
add:
subq , %rsp
addl %edx, %esi
movl , %edx
movl %esi, 12(%rsp)
leaq 12(%rsp), %rsi
call memcpy@PLT
addq , %rsp
ret
在此模式下,add
中对 memcpy
的调用与 main
中对 printf
的调用被视为相同。您的编译器可能有类似的选项。