在 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
我真的只对这里的两个问题感兴趣。
Why is this happening?
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
,让你的朋友惊叹不已。
我正在为我的几个朋友整理一个 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
我真的只对这里的两个问题感兴趣。
Why is this happening?
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
,让你的朋友惊叹不已。