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 中的每个函数,如 printfputs 都是从 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 的调用被视为相同。您的编译器可能有类似的选项。