atp*_*atp 131 c initialization declaration
如果在CI写:
int num;
Run Code Online (Sandbox Code Playgroud)
在我分配任何东西之前num,是num不确定的价值?
bdo*_*lan 180
静态变量(文件范围和函数静态)初始化为零:
int x; // zero
int y = 0; // also zero
void foo() {
static int x; // also zero
}
Run Code Online (Sandbox Code Playgroud)
非静态变量(局部变量)是不确定的.在分配值之前读取它们会导致未定义的行为.
void foo() {
int x;
printf("%d", x); // the compiler is free to crash here
}
Run Code Online (Sandbox Code Playgroud)
在实践中,它们最初只是有一些荒谬的价值 - 有些编译器甚至可能会在调试器中查看特定的固定值以使其显而易见 - 但严格来说,编译器可以自由地执行任何操作,从崩溃到召唤恶魔通过你的鼻腔通道.
至于为什么它是未定义的行为而不是简单的"未定义/任意值",有许多CPU架构在其各种类型的表示中具有额外的标志位.一个现代的例子是Itanium,它的寄存器中有一个"Not a Thing"位 ; 当然,C标准起草人正在考虑一些较旧的架构.
尝试使用设置了这些标志位的值可能会导致操作中的CPU异常确实不应该失败(例如,整数加法或分配给另一个变量).如果你继续保留未初始化的变量,编译器可能会设置这些标志位的随机垃圾 - 意味着触摸未初始化的变量可能是致命的.
Dig*_*oss 57
C一直非常具体地说明对象的初始值.如果是全球性的static,它们将被归零.如果auto,该值是不确定的.
在C89之前的编译器中就是这种情况,并且由K&R和DMR的原始C报告中指定.
这是C89中的情况,请参见6.5.7初始化.
如果具有自动存储持续时间的对象未明确初始化,则其值不确定.如果具有静态存储持续时间的对象未明确初始化,则会对其进行隐式初始化,就好像每个具有算术类型的成员都被赋值为0,并且每个具有指针类型的成员都被赋予空指针常量.
在C99中就是这种情况,参见6.7.8初始化部分.
如果未显式初始化具有自动存储持续时间的对象,则其值不确定.如果没有显式初始化具有静态存储持续时间的对象,则:
- 如果它具有指针类型,则将其初始化为空指针;
- 如果它有算术类型,则初始化为(正或无符号)零;
- 如果是聚合,则根据这些规则初始化(递归)每个成员;
- 如果它是一个联合,则根据这些规则初始化(递归)第一个命名成员.
至于究竟什么是不确定的,我不确定C89,C99说:
3.17.2
不确定值
或未指定值或陷阱表示
但无论标准说什么,在现实生活中,每个堆栈页面实际上都是从零开始,但是当你的程序查看任何auto存储类值时,它会看到你自己的程序在上次使用这些堆栈地址时留下的任何内容.如果你分配了很多auto数组,你会看到它们最终用零完全开始.
你可能想知道,为什么会这样?不同的SO答案处理该问题,请参阅:https://stackoverflow.com/a/2091505/140740
AnT*_*AnT 11
它取决于变量的存储持续时间.具有静态存储持续时间的变量始终隐式初始化为零.
对于自动(本地)变量,未初始化的变量具有不确定的值.除其他外,不确定值意味着您可能"看到"该变量中的"值"不仅不可预测,甚至不能保证稳定.例如,在实践中(即忽略UB一秒钟)此代码
int num;
int a = num;
int b = num;
Run Code Online (Sandbox Code Playgroud)
不保证变量a并且b将接收相同的值.有趣的是,这不是一些迂腐的理论概念,这在实践中很容易作为优化的结果发生.
所以一般来说,流行的答案"它是用内存中的垃圾进行初始化"甚至都不是很正确的.未初始化的变量的行为是一个变量的不同初始化垃圾.
Ubuntu 15.10,内核4.2.0,x86-64,GCC 5.2.1示例
足够的标准,让我们来看一个实现:-)
局部变量
标准:未定义的行为.
实现:程序分配堆栈空间,并且永远不会将任何内容移动到该地址,因此以前使用的是什么.
#include <stdio.h>
int main() {
int i;
printf("%d\n", i);
}
Run Code Online (Sandbox Code Playgroud)
编译:
gcc -O0 -std=c99 a.c
Run Code Online (Sandbox Code Playgroud)
输出:
0
Run Code Online (Sandbox Code Playgroud)
和反编译:
objdump -dr a.out
Run Code Online (Sandbox Code Playgroud)
至:
0000000000400536 <main>:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: 48 83 ec 10 sub $0x10,%rsp
40053e: 8b 45 fc mov -0x4(%rbp),%eax
400541: 89 c6 mov %eax,%esi
400543: bf e4 05 40 00 mov $0x4005e4,%edi
400548: b8 00 00 00 00 mov $0x0,%eax
40054d: e8 be fe ff ff callq 400410 <printf@plt>
400552: b8 00 00 00 00 mov $0x0,%eax
400557: c9 leaveq
400558: c3 retq
Run Code Online (Sandbox Code Playgroud)
根据我们对x86-64调用约定的了解:
%rdi是第一个printf参数,因此是"%d\n"地址处的字符串0x4005e4
%rsi因此是第二个printf参数i.
它来自-0x4(%rbp),这是第一个4字节的局部变量.
此时,rbp堆栈的第一页已经由内核分配,因此为了理解该值,我们将查看内核代码并找出它设置的内容.
TODO是否会在进程终止时将内存设置为其他进程之前将内存设置为某种内容?如果没有,新进程将能够读取其他已完成程序的内存,泄漏数据.请参阅:未初始化的值是否存在安全风险?
然后我们也可以使用自己的堆栈修改并编写有趣的内容,例如:
#include <assert.h>
int f() {
int i = 13;
return i;
}
int g() {
int i;
return i;
}
int main() {
f();
assert(g() == 13);
}
Run Code Online (Sandbox Code Playgroud)
全局变量
标准:0
实施:.bss部分.
#include <stdio.h>
int i;
int main() {
printf("%d\n", i);
}
gcc -00 -std=c99 a.c
Run Code Online (Sandbox Code Playgroud)
编译为:
0000000000400536 <main>:
400536: 55 push %rbp
400537: 48 89 e5 mov %rsp,%rbp
40053a: 8b 05 04 0b 20 00 mov 0x200b04(%rip),%eax # 601044 <i>
400540: 89 c6 mov %eax,%esi
400542: bf e4 05 40 00 mov $0x4005e4,%edi
400547: b8 00 00 00 00 mov $0x0,%eax
40054c: e8 bf fe ff ff callq 400410 <printf@plt>
400551: b8 00 00 00 00 mov $0x0,%eax
400556: 5d pop %rbp
400557: c3 retq
400558: 0f 1f 84 00 00 00 00 nopl 0x0(%rax,%rax,1)
40055f: 00
Run Code Online (Sandbox Code Playgroud)
# 601044 <i>说这i是在地址0x601044和:
readelf -SW a.out
Run Code Online (Sandbox Code Playgroud)
包含:
[25] .bss NOBITS 0000000000601040 001040 000008 00 WA 0 0 4
Run Code Online (Sandbox Code Playgroud)
它说0x601044是在该.bss部分的中间,从那里开始0x601040并且是8个字节长.
然后,ELF标准保证命名的部分.bss完全用零填充:
.bss此部分包含有助于程序内存映像的未初始化数据.根据定义,当程序开始运行时,系统用零初始化数据.该部分不占用文件空间,如部分类型所示SHT_NOBITS.
此外,类型SHT_NOBITS是有效的,并且在可执行文件上不占用空间:
sh_size该成员给出了部分的大小(以字节为单位).除非部分类型SHT_NOBITS,该部分占用sh_size文件中的字节.类型的一部分SHT_NOBITS可能具有非零大小,但它在文件中不占用空间.
然后由Linux内核在启动时将程序加载到内存中时将该内存区域归零.
| 归档时间: |
|
| 查看次数: |
123078 次 |
| 最近记录: |