struct 如何从堆栈复制到 GNU 中的未初始化数据段?

How is struct copied from stack to uninitialized data segment in GNU as?

有了这个简单的 c:

#include <stdio.h>

struct foo{
    int a;
    char c;
};

static struct foo save_foo;

int main(){
    struct foo foo = { 97, 'c', };
    save_foo = foo;

    printf("%c\n",save_foo.c);
}

这里的 save_foo 变量在 bss 段中,在 main 函数中,我试图从堆栈变量 foo “复制”到未初始化的 save_foo.所以我希望这两个元素 foo.a foo.c 被复制到 save_foo.asave_foo.c.

然而,生成的程序集:

.text
    .local  save_foo
    .comm   save_foo,8,8
    .section    .rodata
.LC0:
    .string "%c\n"
    .text
    .globl  main
    .type   main, @function
main:
    endbr64 
    pushq   %rbp    #
    movq    %rsp, %rbp  #,
    subq    , %rsp   #,
# a.c:11:   struct foo foo = { 97, 'c', };
    movl    , -8(%rbp)   #, foo.a
    movb    , -4(%rbp)   #, foo.c
# a.c:12:   save_foo = foo;
    movq    -8(%rbp), %rax  # foo, tmp86

##################################################################
    #MISSING to copy foo.c to save_foo.c yet able to use that value
    
     #movq -4(%rbp), %rcx
     #movq  %rcx, 4+save_foo(%rip)
##################################################################

    movq    %rax, save_foo(%rip)    # tmp86, save_foo
# a.c:14:   printf("%c\n",save_foo.c);
    movzbl  4+save_foo(%rip), %eax  # save_foo.c, _1
# a.c:14:   printf("%c\n",save_foo.c);
    movsbl  %al, %eax   # _1, _2
    movl    %eax, %esi  # _2,
    leaq    .LC0(%rip), %rdi    #,
    movl    [=11=], %eax    #,
    call    printf@PLT  #
    movl    [=11=], %eax    #, _9
# a.c:15: }
    leave   
    ret 
    .size   main, .-main
    .ident  "GCC: (Ubuntu 10.2.0-13ubuntu1) 10.2.0"
    .section    .note.GNU-stack,"",@progbits
    .section    .note.gnu.property,"a"
    .align 8
    .long    1f - 0f
    .long    4f - 1f
    .long    5
0:
    .string  "GNU"
1:
    .align 8
    .long    0xc0000002
    .long    3f - 2f
2:
    .long    0x3
3:
    .align 8
4:

只复制了一个元素(foo.a)。但是 foo.c 而不是 movzbl 4+save_foo(%rip), %eax 如何获得正确的值(99,它是 ASCII 'c'),而该值是 而不是 复制的? (没有 movl from -4(%rbp) where the value is to 4+save_foo(%rbp) symbol on the bss segment)。不应该将 4+save_foo(%rbp) 处的值归零(未初始化时)?

movq指令会复制8个字节,所以整个struct foo的数据都复制到这里:

    movq    -8(%rbp), %rax  # foo, tmp86
    movq    %rax, save_foo(%rip)    # tmp86, save_foo

movq -8(%rbp), %rax 是整个结构的 8 字节重新加载。 注意 lq 操作数大小后缀,以及也指示操作数大小的寄存器名称。 (Assembly registers in 64-bit architecture)

当您要求 GCC 通过执行 C 结构分配来复制整个对象时,它会使用更宽的 regs,最多 16 字节的 XMM regs,就像 memcpy 一样。 (或者对于足够大的东西,可能会插入一个 call memcpy 而不是内联扩展它。)

您建议的 movq %rcx, 4+save_foo(%rip) 将存储 8 个字节,从全局的一半开始,因此它会写入它之外。

如果你想像 save_foo.a = foo.a; save_foo.c = foo.c; 一样分别做两半,你会使用 %eax%ecx,或者 %ecx 两次。 (使用 movl,而不是 movq)。或者可能是 movzbl 字节加载和 movb 字节或 movl 双字存储,这取决于 GCC 是否选择覆盖目标中的填充,就像复制整个对象时所做的那样.