关在C叠memcopying数据

问题描述:

我组建了一个C谜语一对夫妇的朋友时,一个朋友把我的注意力的事实,下面的代码片段(这恰好是我一直在写谜语的一部分)编译并在OSX关在C叠memcopying数据

#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,随后是两个随机数。经过一些调试并查看编译器生成的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 $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. 是否有任何GET-变通这个问题?

我知道这是不是利用堆栈,因为我是一个专业的C语言开发,这是真的,我发现这个问题有趣的一些我的时间投入到唯一理由一条可行之路。

+2

您正在调用未定义的行为。所有投注都关闭。就个人而言,我不希望输出“10 20 30”。相反,我期望'1͉̟̗̗͕͊̅̋̇̽̊̔͠0̶̨̳̖̰̥͍̞͛̇̒̌̋̏̚͠2̶̨̼̰͖̬̒̓͟͞͡͞0̳͔̩̰̼̙͓͓̗͌̊̔͐̌̚͠3̗͔̰̥͉͎̦̦͋͊̓͛͢͜Ȟ̳̖͙͓̻͉̈͊̑̓͢͞͝Ę̛̯̣̞̰͉̗͐̑̍̓̔Ç̵͔̝͉͇͖̾͆̐̐̽̓̕͘͜Ỏ̵̰̣̳̤̪̅̄̌̈̐̅͌̚͞M͖̪̝͈͇͙͛̽͊̈̔͝Ę̘̺̙͇̦̖̯̜̿̊̅̔̑Ş̵̡̢̳̔̾̅̽̕̕͜͝' – Cornstalks

+0

这种未定义的行为如何?我给'memcpy'开始一个内存地址,读取的字节数为 –

+1

@ FadiHannaAL-Kass - 在'a'的地址处只有一个元素,但是您正在读取3个元素('b'和'在某些平台上)。这是未定义的行为。正如你所见,'a','b'和'c'可能不是连续的。另请参阅[是否未定义的行为来访问阵列超出其最终,如果该区域分配?](https://stackoverflow.com/q/12354413/608639) – jww

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

+0

那么,在这种情况下,gcc被迫将三个值存储在内存中,因为'b'和'c'是易失性的,'a'通过引用'memcpy'传递。虽然你的答案的其余部分是有道理的 –

+0

@ FadiHannaAL-Kass是真的,错过了那个细节 - 编辑 –

+0

@ FadiHannaAL-Kass只要声明它'volatile'还不够。任何智能编译器都会丢弃“volatile”,因为不会采用地址。 –

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

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

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

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

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

尝试gcc --version在两个系统上,并惊讶你的朋友。