在循环内创建的变量在 C 中的迭代期间更改值
Posted
技术标签:
【中文标题】在循环内创建的变量在 C 中的迭代期间更改值【英文标题】:Variable created inside loop changes value during iterations in C 【发布时间】:2016-05-23 05:30:00 【问题描述】:我的产品中有类似以下的代码。据我说,输出是'0 1 2 3'。但是类似代码的输出是'1 1 1 1'。
for(i = 0 ;i < 5;i++)
int j;
if(i)
printf("%d ",j);
j = i;
我的理解是 j 在整个“for”循环期间只在堆栈上分配一次,并且在迭代期间使用相同的值。另外,如果我将 j 的声明移到 for 循环之外,我会得到预期的结果。我在这里错过了什么?
PS - 当我在我的个人机器上运行相同的代码时,我得到了预期的输出。但在生产上就不同了。
【问题讨论】:
【参考方案1】:首先,为了明确关于自动局部变量的存储持续时间,让我引用C11
标准,第 6.2.4 章,(强调我的)
一个对象,其标识符被声明为没有链接且没有存储类 说明符
static
具有自动存储持续时间,[...]
和,
对于这样一个没有变长数组类型的对象,它的生命周期会延长 从进入与其关联的块直到该块的执行结束 反正。 (进入封闭块或调用函数会暂停,但不会结束, 执行当前块。) 如果块是递归进入的,一个新的实例 每次都会创建对象。对象的初始值是不确定的。
因此,在您的代码中,每次迭代都会获得j
的新实例。没有任何保留。
在您的代码中,
int j; //not initialized
if(i)
printf("%d ",j); //this one here
您正在尝试使用具有不确定值的未初始化的自动局部变量j
。它调用undefined behavior。
根据C11
,第 §6.7.9 章
如果具有自动存储持续时间的对象未显式初始化,则其值为 不确定
及相关,对于 UB,附件 §J.2
具有自动存储持续时间的对象的值在它被使用时使用 不确定。
一旦您的代码到达 UB,无论如何,输出就无法证明是正确的。
OTOH,当您在循环外声明 j
时,它具有函数范围。然后,与上述情况不同,对于循环的所有迭代,将只有 一个 j
实例。
根据执行流程,第一次,i
为 0,if
将评估为 false,printf()
将被跳过,j
将被初始化。然后,在下一次迭代中,当您点击printf()
时,j
被初始化,此后一切都很好。
【讨论】:
另外,我认为如果i=0
,if
声明不起作用。
不会只有UB第一次通过循环吗?在第一个循环之后,分配了 J。
@RobertHarvey Nopes,每个j
都是独一无二的。请参阅§6.2.4/6
那么OP关于所有循环迭代中使用相同J的假设一定是无效的。我希望每次都会创建一个新的 J,所以我对 OP 的断言有点惊讶,但我不是 C 专家。
@Holsety 不,它重复使用相同的空间。从概念上讲,当执行到达int j;
时分配变量,并在到达以下
时释放。编译器将通过将特定的堆栈位置分配给 j
来优化它【参考方案2】:
为了清楚起见,我认为 for 循环将在后台转换为:
i = 0;
LoopStart:
if(!(i<5)) goto LoopEnd;
int j;
if(i)
printf("%d ",j);
j = i;
i++;
goto LoopStart;
LoopEnd:
实际的实现会有所不同,但这有助于强调这一点: 对于循环的每次迭代,都会进入并退出该块,这意味着在此示例中,该块中的每个 auto 都被创建 和 5 次。 正如其他人提到的,这意味着您每次在 printf 中都使用未初始化的 j。
至于为什么代码可能在某些平台/编译器上工作。这可能是因为 j 每次都分配了相同的堆栈地址,并且编译器在创建或销毁 j 时不会清除堆栈,所以恰好分配给旧的死 j 的最后一个值可以通过新的访问,未初始化的。
【讨论】:
只是吹毛求疵:当goto
可以明显避免时,我不会提倡使用它。
天哪,我不是 :P 只是用它来说明循环确实退出循环块并因此破坏块的本地汽车 每次循环迭代
以上是关于在循环内创建的变量在 C 中的迭代期间更改值的主要内容,如果未能解决你的问题,请参考以下文章