mor*_*imn 0 c malloc undefined-behavior
我知道这是一个非常基本的问题,可能有重复,但我找不到这个特定问题的严格答案,该问题涉及标准。(我看到有人说是UB,有人说不是)
如果我分配一块内存而不向其中填充数据,
int* ptr = malloc(10 * sizeof(int));
Run Code Online (Sandbox Code Playgroud)
然后尝试读取它,那里的值将是垃圾。
但这是否被归类为未定义行为?或者它只是糟糕但至少不是UB?
读取未初始化内存的行为本身malloc并不是未定义的。如果使用非字符类型读取包含陷阱表示的内存,则可能会导致未定义的行为,但只有当该类型具有陷阱表示时才会发生这种情况。(大多数现代 C 实现没有整数类型的陷阱表示。)
然而,虽然它不是完全未定义的,但也不是完全定义的。尝试读取未初始化的内存不需要实际读取内存。
C 2018 7.22.3.4 2 说,malloc带参数的函数size:
该
malloc函数为size大小指定且值不确定的对象分配空间。
C 3.19.2 1 将不确定值定义为:
未指定的值或陷阱表示
C 3.19.3 1 将未指定值定义为:
相关类型的有效值,其中本文档不要求在任何情况下选择哪个值
这其中没有任何内容使行为未定义。
根据 6.2.6.1 5,C 标准未定义使用非字符类型读取陷阱表示的行为。因此,如果使用具有陷阱表示的类型读取内存,并且结果位恰好包含表示陷阱的值,则行为未定义。
整数类型的陷阱表示在现代 C 实现中很少见。许多年前,一些系统会保留某些位模式,例如 16 位 8000 16,以表示未初始化或无效的数据,并且尝试在算术中使用这样的值会产生陷阱。在某些类型 T 中没有陷阱表示的 C 实现中,通过类型 T 访问未初始化的数据不会遇到陷阱表示。因此结果必须是该类型的未指定(因此有效)值。
此外,C 标准中没有其他内容会使此行为未定义。6.3.2.1 2 中有一条规则,访问一个自动存储期的未初始化对象,如果不取其地址,则具有未定义的行为。但是,提供的内存malloc已经分配了存储时间,不是自动的。(该规则是对某些 Hewlett-Packard 硬件的一种适应,能够将寄存器标记为未初始化并在使用时进行捕获。)
此外,无论其成员的类型如何,整个结构和联合永远不会是陷阱表示。现代 C 实现中最常见的陷阱表示是浮点信号 NaN(非数字)。
请注意,分配的内存中的值是未指定的,并且上面的定义指出“本文档没有强加在任何情况下选择哪个值的要求。” 这意味着如果你这样做:
unsigned *p = malloc(sizeof *p);
printf("%u\n", *p);
printf("%u\n", *p);
Run Code Online (Sandbox Code Playgroud)
C 标准不要求*p在第一个中选择哪个值printf,也不要求在第二个中选择哪个值printf,甚至不要求它们彼此相同。一个“未指定的值”可能表现得好像它的位在时不时地自行改变。所以,行为不是未定义的——它不能允许“任何事情”发生在你的程序中;你的程序不能突然跳转到一个不同的函数或清除其他数据——但它也没有被定义为像内存具有固定值的位一样。
这意味着您不能可靠地读取未初始化的内存——不能保证读取内存产生实际在物理内存中的位。
要了解为什么 C 标准允许程序像内存中的位可能会发生变化一样,请考虑以下代码:
unsigned a = *p + 3;
unsigned b = *p + 4;
Run Code Online (Sandbox Code Playgroud)
对于正常情况下的代码,编译器可能会生成如下程序集:
// As we start, registers r7, r8, and r9 already contain p,
// the address of a, and the address of b, respectively.
load r3, (r7) // Get value of *p from memory.
add r3, #3 // Add 3.
store r3, (r8) // Store sum to a.
load r3, (r7) // Get value of *p from memory.
add r3, #4 // Add 4.
store r3, (r9) // Store sum to b.
Run Code Online (Sandbox Code Playgroud)
如果内存中碰巧包含 0,那么这些指令将在 ba和4b 中存储 3 。然而,未初始化的内存不需要表现得好像它有一个固定值的规则意味着允许编译器的优化消除加载指令。假设,这可能会导致如下指令:
add r3, #3 // Add 3.
store r3, (r8) // Store sum to a.
add r3, #4 // Add 4.
store r3, (r9) // Store sum to b.
Run Code Online (Sandbox Code Playgroud)
如果r3在此代码序列开始时恰好包含 0,则 3 将存储在 中a,7 将存储在 中b。没有可能的值*p会导致*p + 33 和*p + 47。所以这段代码就像*p自己改变了一样。
在实践中,优化不仅会删除这里的加载指令,也不会识别后续指令也与固定值断开连接并删除它们。然而,现实世界的优化比这更复杂。C 标准授予的许可证允许编译器删除它可以确定未使用定义值的代码部分,即使它无法确定程序的所有内容。
| 归档时间: |
|
| 查看次数: |
105 次 |
| 最近记录: |