在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下build立时,你会得到10随后是两个随机数。 经过一些debugging,看着编译器生成的assembly我得出这样的结论,这是由于堆栈是如何构build的。 我绝不是一个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 $10, -12(%rsp) xorl %eax, %eax movl $20, -8(%rsp) movl $30, -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 $16, %rsp movl $20, -8(%rbp) movl $30, -4(%rbp) leaq L_.str(%rip), %rdi movl $10, %esi xorl %eax, %eax callq _printf xorl %eax, %eax addq $16, %rsp popq %rbp retq .cfi_endproc .section __TEXT,__cstring,cstring_literals L_.str: ## @.str .asciz "%d %d %d\n" .subsections_via_symbols 

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

  1. 为什么发生这种情况?

  2. 这个问题有没有解决办法?

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

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

未定义的行为。 你不希望复制10,20,30。 你希望不要seg-fault。

没有什么可以保证a,b和c是顺序存储器地址,这是你的天真假设。 在Linux上,编译器碰巧使它们连续。 你甚至不能依靠gcc总是这样做。

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

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

  • 当你在OS / X版本中运行gcc时,很可能会调用已安装的clang版本。

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