-2

这个简单的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或从 GOT 编辑(即寄存器保存 GOT 的地址)。但不是,它就像“汇编内联指令”而不是常规调用地址。甚至是一个符号?如果是这样,为什么不作为论据?在 GOT 表中吗?如果是这样,从 GOT 到该符号的偏移量是多少?putscall%ripmemcpymemcpycallmemcpy

4

1 回答 1

5

所以首先,你有一个错误:

$ cc -O2 -S test.c
test.c: In function ‘add’:
test.c:7:12: warning: function returns address of local variable

返回局部变量的地址具有未定义的行为,当且仅当调用者使用该值;这就是为什么您的编译器生成返回空指针的代码的原因,如果使用它会使程序崩溃,否则不会造成伤害。事实上,我的 GCC 副本仅生成以下内容add

add:
        xorl    %eax, %eax
        ret

因为对返回值的处理使其他操作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    $24, %rsp
    addl    %edx, %esi
    movl    $4, %edx
    movl    %esi, 12(%rsp)
    leaq    12(%rsp), %rsi
    call    memcpy@PLT
    addq    $24, %rsp
    ret

在这种模式下,对memcpyin的调用被视为与对inadd的调用相同。您的编译器可能有类似的选项。printfmain

于 2020-07-09T13:08:01.200 回答