以下代码片段是否构成未定义行为,因为我在变量声明之前跳转并通过指针使用它?如果是这样,标准之间是否有差异?
int main() {
int *p = 0;
label1:
if (p) {
printf("%d\n", *p);
return 0;
}
int i = 999;
p = &i;
goto label1;
return -1;
}
以下代码片段是否构成未定义行为,因为我在变量声明之前跳转并通过指针使用它?如果是这样,标准之间是否有差异?
int main() {
int *p = 0;
label1:
if (p) {
printf("%d\n", *p);
return 0;
}
int i = 999;
p = &i;
goto label1;
return -1;
}
goto
语句有两个限制:
(c11, 6.8.6.1p1) "goto语句中的标识符应命名在封闭函数的某处的标签。goto语句不得从具有可变类型的标识符的作用域外跳到该标识符的作用域内。"
你没有违反这两个要求,除此之外,没有其他的规定。
请注意,在c99和c90中也是相同的(没有额外的要求)。当然,在c90中,由于声明和语句的混合,程序是无效的。
关于goto
语句后访问i对象的生存期,C语言规定如下(请参考我的强调,下面的其他复制语句对于更棘手的程序可能会很有趣):
(c11, 6.2.4p6) "对于这样一个没有可变长度数组类型的对象,它的生命周期从与其相关联的块入口开始,直到以任何方式结束执行该块。如果递归地进入该块,则每次都创建该对象的新实例。 如果指定了初始化该对象,则在达到该声明或复合字面量的执行时每次执行初始化;否则,每次达到声明时该值都变得不确定。
这意味着,当读取*p
时,i
仍然存在,没有访问任何对象的生命周期之外的内容。
p
指向的对象应该是i
,没有其他原因。 - ouahi
的生命周期在 }
或函数返回时结束。 - ouah{ unsigned char *p; goto LATE; EARLY: printf("%d",(int)*p); return; unsigned char i; printf("%d", (int)*p); return; LATE: p=&i; i=123; goto EARLY; }
,第一个 printf
会显示 123,第二个则会显示不确定的值,因为后退跳转会使 *p
的值变得不确定。请注意,代码从未真正离开 i
的作用域。 - supercatprintf
?我可以建议您提出一个新问题来继续讨论吗? - ouah我会尝试回答您可能一直想问的问题。
您的程序行为是明确定义的。(return -1;
存在问题;仅有0
,EXIT_SUCCESS
和EXIT_FAILURE
被定义为从main
返回的值。但这不是您关心的问题。)
以下是该程序:
#include <stdio.h>
int main(void) {
goto LABEL;
int *p = 0;
LABEL:
if (p) {
printf("%d\n", *p);
}
}
使用 goto
会产生未定义行为。它将控制转移到 p
的作用域内的某个位置,但是绕过了其初始化,因此当执行 if (p)
检查时,p
具有不确定的值。
在您的程序中,p
的值始终是定义良好的。声明在 goto
之前到达,将 p
设置为零(空指针)。if (p)
检查为假,因此第一次不执行语句的主体。在给出定义良好的非空值后,执行 goto
. goto
后,if (p)
检查为真,然后执行 printf
调用。
在您的程序中,p
和 i
的 生命周期 从打开 {
的地方进入,到达闭合 }
或执行 return
语句时结束。每个变量的 作用域(即名称可见的程序文本区域)从其声明到闭合 }
。当 goto
向后传递控件时,变量名称 i
已经超出范围,但是该名称引用的 int
对象仍然存在。名称 p
是在作用域内(因为它早已声明),并且指针对象仍然指向同一个 int
对象(如果该名称可见,则其名称将为 i
)。
请记住,作用域 指的是程序文本区域,在其中名称可见,生命周期 指的是程序执行��间保证对象存在的时间跨度。
通常,如果对象的声明具有初始化项,则可以保证每次其名称可见时都具有有效值(除非稍后将某个无效值分配给它)。这可以通过使用 goto
或 switch
来绕过(但是必须小心使用)。
999
?我没有清楚的解释变量在声明之前的值是什么。此时,i
的生命周期已经开始,但i
超出了范围。另一个例子,如果将return 0;
替换为*p = 998;
,第二次“循环”会回到999吗? - M.Mvoid main(void)
相关的事情充满热情。哦,抱歉,我的意思是:int main(void)
。 - ryykerint i = 999;
之前对象的值是不确定的。这在6.2.4p6中有明确规定。 - ouah6.2.4
节对象的存储期中找到一个很好的例子,它说:
[...]有一个简单的经验法则:当进入块时声明的变量将创建为未指定值,但是当在正常执行过程中到达声明时,初始化程序将被评估并将值放置在变量中。因此,超越声明向前跳转会使其未初始化,而向后跳跃将导致其被初始化多次。如果声明不初始化变量,则即使这不是第一次到达声明,它也会将其设置为未指定的值。
变量的作用域始于其声明。因此,尽管变量在进入块时存在,但在到达其声明之前不能用名称引用它。
并提供以下示例:
int j = 42;
{
int i = 0;
loop:
printf("I = %4d, ", i);
printf("J1 = %4d, ", ++j);
int j = i;
printf("J2 = %4d, ", ++j);
int k;
printf("K1 = %4d, ", k);
k = i * 10;
printf("K2 = %4d, ", k);
if (i % 2 == 0) goto skip;
int m = i * 5;
skip:
printf("M = %4d\n", m);
if (++i < 5) goto loop;
}
输出结果如下:
I = 0, J1 = 43, J2 = 1, K1 = ????, K2 = 0, M = ????
I = 1, J1 = 44, J2 = 2, K1 = ????, K2 = 10, M = 5
I = 2, J1 = 45, J2 = 3, K1 = ????, K2 = 20, M = 5
I = 3, J1 = 46, J2 = 4, K1 = ????, K2 = 30, M = 15
I = 4, J1 = 47, J2 = 5, K1 = ????, K2 = 40, M = 15
它说:
其中“????”表示不确定的值(使用不确定的值是未定义的行为)。
这个例子符合草案C99标准第 6.2.4
节对象的存储期第 5 段的规定:
对于这样一个没有可变长度数组类型的对象,它的生命周期从与其关联的块输入开始,直到以任何方式结束执行该块。 (进入封闭块或调用函数暂停,但不结束当前块的执行。)如果递归地进入该块,则每次都会创建该对象的新实例。 该对象的初始值是不确定的。 如果为对象指定了初始化,则每次在块的执行中达到声明时都会执行初始化; 否则,每次达到声明时,该值都会变得不确定。
p = &i
这一行,以及在printf()
中是否可以读取p
。 - Kijewski