在 C 中从堆栈中复制数据

memcopying data off the stack in C

我正在为我的几个朋友整理一个 C 谜语,当一个朋友提醒我注意以下片段(恰好是我一直在写的谜语的一部分)时运行 编译时不同,运行 在 OSX

上不同
#include <stdio.h>
#include <string.h>

int main()
{
        int a = 10;
        volatile int b = 20;
        volatile int c = 30;

        int data[3];

        memcpy(&data, &a, sizeof(data));

        printf("%d %d %d\n", data[0], data[1], data[2]);
}

您期望的输出是 10 20 30这恰好是 Linux 下的情况,但是当构建代码时在 OSX 下,您会得到 10 后跟两个 运行dom 数字。经过一些调试并查看编译器生成的 assembly 我得出结论,这是由于堆栈的构建方式所致。我绝不是 assembly 专家,但是在 Linux 上生成的汇编代码似乎很容易理解,而在 OSX 上生成的汇编代码让我有点失望。也许我可以从这里得到一些帮助。

这是在 Linux 上生成的代码:

        .file   "code.c"
        .section        .text.unlikely,"ax",@progbits
.LCOLDB0:
        .section        .text.startup,"ax",@progbits
.LHOTB0:
        .p2align 4,,15
        .globl  main
        .type   main, @function
main:
.LFB23:
        .cfi_startproc
        movl    , -12(%rsp)
        xorl    %eax, %eax
        movl    , -8(%rsp)
        movl    , -4(%rsp)
        ret
        .cfi_endproc
.LFE23:
        .size   main, .-main
        .section        .text.unlikely
.LCOLDE0:
        .section        .text.startup
.LHOTE0:
        .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.4) 5.4.0 20160609"
        .section        .note.GNU-stack,"",@progbits

这是在 OSX:

上生成的代码
        .section        __TEXT,__text,regular,pure_instructions
        .macosx_version_min 10, 12
        .globl  _main
        .p2align        4, 0x90
_main:                                  ## @main
        .cfi_startproc
## BB#0:
        pushq   %rbp
Ltmp0:
        .cfi_def_cfa_offset 16
Ltmp1:
        .cfi_offset %rbp, -16
        movq    %rsp, %rbp
Ltmp2:
        .cfi_def_cfa_register %rbp
        subq    , %rsp
        movl    , -8(%rbp)
        movl    , -4(%rbp)
        leaq    L_.str(%rip), %rdi
        movl    , %esi
        xorl    %eax, %eax
        callq   _printf
        xorl    %eax, %eax
        addq    , %rsp
        popq    %rbp
        retq
        .cfi_endproc

        .section        __TEXT,__cstring,cstring_literals
L_.str:                                 ## @.str
        .asciz  "%d %d %d\n"


.subsections_via_symbols

我真的只对这里的两个问题感兴趣。

  1. Why is this happening?

  2. Are there any get-arounds to this issue?

我知道这不是利用堆栈的实用方法,因为我是一名专业的 C 开发人员,这确实是我发现这个问题很有趣并投入一些时间的唯一原因。

访问声明变量末尾之后的内存是未定义的行为 - 无法保证当您尝试这样做时会发生什么。由于编译器如何在 Linux 下生成程序集,您碰巧直接在堆栈的一行中获取了 3 个变量,但是这种行为只是巧合 - 编译器可以合法地在变量之间添加额外数据堆栈或真正做任何事情——结果不是由语言标准定义的。所以在回答你的第一个问题时,它正在发生,因为你正在做的不是设计语言的一部分。在回答你的第二个问题时,没有办法从多个编译器可靠地获得相同的结果,因为编译器没有被编程为可靠地重现未定义的行为。

未定义的行为。您不希望复制 10、20、30。你希望不要出现段错误。

没有什么可以保证 a、b 和 c 是顺序内存地址,这是您天真的假设。在 Linux,编译器碰巧使它们顺序排列。你甚至不能指望 gcc 总是这样做。

您已经知道该行为未定义。在 OS/X 和 Linux 上行为 不同 的一个很好的理由是这些系统使用不同的编译器,生成不同的代码:

  • 当您在 Linux 中 运行 gcc 时,您调用安装的 Gnu C 编译器版本。

  • 当您在 OS/X 版本中 运行 gcc 时,您很可能会调用 clang.[=14= 的已安装版本]

在两个系统上尝试gcc --version,让你的朋友惊叹不已。