假设我有一段非常简单的代码(在这个代码中n = 128,但假设数组大小可以是任何内容):
#include <stdlib.h>
#include <stdio.h>
int main() {
int *p = (int *)malloc(sizeof(int) * 128);
for (int i = 0; i < 128; i++) {
printf("%dn", p[i]);
printf("%dn", p[i]);
}
return 0;
}
我知道(所以请不要在评论中提及这一点)p
指向的内存块是未初始化的,因此该程序的输出可以是任何东西。
我的问题是:对于每个n
,对所有0 <= i < n
p[i]
的结果是否一致? 更具体地说,对于p[i]
的每次读取(在二进制文件的 SAME 调用中),等效i
的结果是否相同?
换句话说,上述循环的给定迭代中的两个printf
语句会打印相同的值吗?
如果你要这样做:
int *p = (int *)malloc(sizeof(int) * 128);
for (int i = 0; i < 128; i++) {
printf("%dn", p[i]);
printf("%dn", p[i]);
}
您调用未定义的行为,因为您读取了不确定的值。 一旦你调用了未定义的行为,所有的赌注都会被关闭。 程序可能会崩溃,可能会表现出奇怪的行为,或者看起来可以正常工作。
因此,C 标准不保证对printf
的两个调用将在上面的代码中打印相同的值,即使许多实现可能会。
至于为什么这是未定义的行为有点棘手。malloc
返回的字节(以及未初始化的局部变量)具有不确定的值。 这意味着它可以是未指定的值(表示任何值)或陷阱表示形式。 读取陷阱表示形式是导致未定义行为的原因。
陷阱表示不表示给定数据类型的有效值的位模式。 如果读取陷阱表示形式,某些 CPU 将触发故障。
但是,字符类型有一个例外,即char
、signed char
和unsigned char
。 这些类型不能具有陷阱表示形式。
从C标准关于类型的第6.2.5节:
15
char
、signed char
和unsigned char
这三种类型统称为字符类型。实施应 定义char
具有与 相同的范围、表示形式和行为 要么signed char
要么unsigned char
.
从关于类型表示的第 6.2.6 节:
某些对象表示不需要表示 对象类型。如果对象的存储值具有这样的 表示形式,并由没有 字符类型,则行为未定义。如果这样的表示是 由修改对象全部或任何部分的副作用产生 通过没有字符类型的左值表达式, 行为是未定义的。50) 这样的表示称为陷阱 表示法。
因此,如果您按如下方式使用这些类型之一:
unsigned char *p = malloc(128);
for (int i = 0; i < 128; i++) {
printf("%un", p[i]);
printf("%un", p[i]);
}
这不是未定义的行为。 这些值只是未指定而不是不确定,并且对printf
的两个调用保证打印相同的值。
我的问题是:对于每个 n,所有 0 的 p[i] 的结果<= i
在某些系统上,这将是相同的。在其他人身上,它不会。
不,它不会。它们的价值将是不确定的。因此,每次运行可能会有所不同。在代码中使用它们将调用未定义的行为。如果它包含一些垃圾值 - 如果你想在它们之间获得统一性,那就是错误的期望。不,他们不会。
您可以使用calloc
来使用0
初始化分配的内存。但malloc
返回未初始化的内存地址。它的价值是不确定的。
来自标准:§7.22.3.4
malloc
函数为大小由大小指定且值不确定的对象分配空间。
编辑:
OP 询问在二进制文件的特定调用中值是否始终相同?(两段都澄清了这一点)
假设未初始化的内存中有阅读它是未定义的行为。(我们不应该认为取消程序的范围 - 在程序中,我们会通过阅读它来知道它的价值是什么 - 并且阅读它是UB)。100
,并且在执行某些操作后,如果您没有为其分配任何内容 - 那么值将是相同的。
但是没有用——即使这样。你不能使用它。因为使用它是未定义的行为。
现在你怎么知道它们是相同的?你会考虑打印它。但是如果你打印它 - 那么它将是一个未定义的行为。读取未初始化的值是 UB。(你的方式。正确的表达方式是 - 当它是 UB 时,行为将不一致。(楚克斯指出了这一点)。
,上述循环的给定迭代中的两个 printf 语句会打印相同的值吗?
不能保证。
尝试读取不确定值会调用未定义的行为 - 允许任何结果(包括段错误)。
在基于 x86 的系统上,您很可能会看到两次打印相同的值(假设它在第一个值上没有段错误)。 但是,这不是您应该期望或依赖的行为 - 这是一个编码错误,句号。
您看到的值只是初始化数组的解释二进制值。它可能是写在同一个地方的旧数据的幽灵。
通过malloc()
分配的对象具有"分配"的存储持续时间和(最初)不确定的值(C2011,7.22.3.4/2)。 与这里的一些主张相反,读取不确定的值并不一定会产生未定义的行为。 此外,不确定的值并不意味着它可以在程序运行时任意更改。 它只是意味着该值是
未指定的值或陷阱表示形式
(C2011 3.19.2/1)
问题在于陷阱表示的可能性。 具体说来:
某些对象表示不需要表示 对象类型。如果对象的存储值具有这样的 表示形式,并由没有 字符类型,则行为未定义。如果这样的表示是 由修改对象全部或任何部分的副作用产生 通过没有字符类型的左值表达式, 行为未定义。这样的表示称为陷阱 表示法。
(C2011, 6.2.6.1/5)
您的代码确实通过 lvalue 表达式p[i]
读取不确定值。如果这些值中的任何一个恰好是陷阱表示形式,则程序的行为是未定义的。 否则,标准中没有理由不正常且可重复地读取(未指定的)值。
现在事情是这样的:在抽象的意义上,你的程序因此有可能表现出未定义的行为。 如果它确实表现出未定义的行为,则所有赌注都已关闭,特别是,分配对象中的int
值可能会在没有警告的情况下更改,或者似乎会更改。 因此,C 确实不能保证您的程序将打印相同数字对。
另一方面,很少有 C 实现提供int
类型来提供任何陷阱表示,而你的不太可能是陷阱表示。 如果您的实现不提供任何类型int
的陷阱表示形式,则不确定已分配对象的值不会导致程序中的p[i]
表达式在该实现上运行时产生未定义的行为。 在具有该共同特征的符合实现上,程序将打印重复值对。