我已经用C编写了一个程序,我不小心将一个未初始化的变量int放入其中。此int编写在函数内部,但是以某种方式在递增后打印时,它可以正常工作。为什么是这样?是不是应该是未定义的行为,实际上就是打印垃圾?
int n;
int counter;
for (counter = 0; limit > counter; counter++) {
++n;
printf("%d\n", n);
}
答案 0 :(得分:2)
访问未初始化变量的值是不确定的行为。大多数编译器至少会对此发出警告,MSVC 2017默认将其视为错误。
您无从得知它最初包含的内容,并且如果它恰好包含了您打算立即进行的循环迭代的数量,那么将其用作循环保护而不先为其分配值似乎会起作用程序中正在运行的实例。
答案 1 :(得分:2)
好吧,如果保证失败,那么它就不会是不确定的行为(a),它将被定义为失败。从字面上看,UB意味着任何事情都可能发生,包括它按预期工作的可能性。
这并不意味着可以依靠它。您仍应避免使用UB,仅因为它可能经常以怪异的方式出现错误。实际上,您的代码完全有权利打印合理的值,同时还默默修改办公桌上的随机Excel电子表格:-)
(a)参见C11 6.7.9 Initialization /10
:
如果未自动初始化具有自动存储期限的对象,则其值不确定。
还有C11 J.2 Undefined behaviour
:
使用具有自动存储持续时间的对象的值 不确定。
答案 2 :(得分:0)
在编写标准时,大多数形式的“未定义行为”都认识到以下事实:旨在用于不同平台和目的的实现将以不同的方式运行-有些有用且可预测,而有些则没有,并认识到“市场” [如C Rationale文件所描述的]比委员会更适合判断哪些实现应以何种方式运行。
从历史上看,在大多数平台上,编译器的行为就像自动对象以某种任意方式初始化一样,不需要花费太多,因为这种一致性过于一致以至于不能用作任何种类的随机数生成器,但可靠的可预测性足以用于其他任何用途,除非可能的值与其他任何用途一样 [例如因为通常不考虑对象是否具有有用的价值而复制对象可能比避免不持有该对象而复制它便宜。但是,在某些平台上,编译器确保此类行为的唯一方法是使其明确初始化此类对象本身。该标准的作者不想要求用于此类平台的编译器使用可能会被程序员覆盖的伪值来初始化对象,而是选择要求程序员其代码必须与此类平台兼容< / em>必须确保没有初始化就不会使用任何东西。
但是,从那时起,事情发展到了两个世界中最糟糕的方向。该标准的作者不要求强制实现应保证自动对象的行为就像在初始化时使用任意值一样,将以零成本提供一些好处,因为他们认为没有理由期望实现可以做任何事情否则在这种情况下。但是,今天,某些编译器将把一个动作作为“未定义的行为”作为事实,假设没有程序会收到会导致该动作的输入。因为这样的假设通常不是很有用,所以它们通常对程序行为没有影响。所有UB都会导致胡说八道的想法(您似乎在暗示)的事实是,使用UB推断事情不会发生的实现(实际上会发生)很可能会在其中生成完全荒谬的代码。这样的情况。例如,积极的优化程序可能会看到类似以下内容的
:void test(int x)
{
int y,z;
if (x == 23)
y=z;
printf("%d\n",x);
}
并推断用23以外的任何x
值调用该函数是“不可能的”,因此应将printf替换为puts("23");
。我认为没有哪个编译器能像现在这样还那么好,但是查看能够生成x
其他值的代码生成似乎很时髦。作为“缺少的优化”。