我想了解以下 C 语言的效果:
int func(int arg) {
if (arg == 0) {
double *d = malloc(...);
}
//...
}
我的理解是:
- 无论
arg
的值如何,调用func
时都会为指针d
腾出栈空间 d
仅被初始化,即调用malloc
,如果arg == 0
d
只能在 if block 内访问;尝试在外部访问它会产生编译错误 - 即使无论如何分配了d
的堆栈空间。
因此,除了阻止访问 if block 之外的范围规则外,它等同于以下内容:
int func(int arg) {
double *d;
if (arg == 0) {
d = malloc(...);
}
//...
}
这是正确的吗?我正在使用 icc
默认设置进行编译,这似乎是 std=gnu89
。
最佳答案
d
表示的对象的生命周期从声明它的 block 的开头开始(可能在声明之前),不一定在函数的开头。实际上,编译器可能会选择在函数入口处为所有变量分配空间;例如,Gcc 将 func
的两个版本编译成相同的程序集。一个函数中只有几个自动变量,很可能它们都放在寄存器中,根本没有堆栈空间用于它们。
初始化发生在初始化器出现的地方。所有这些都遵循 as-if 规则(一如既往):在这种情况下,Gcc 在优化时不会生成任何对 malloc
的调用(从而消除内存泄漏),编译器是允许的“知道”标准库函数的作用。如果这不是一个库函数并且编译器不知道该定义,则该调用保证在到达初始化程序时准确发生。
使用未声明的标识符(或超出范围的标识符)是语法错误,因此会在编译时被捕获。表示对象的生命周期(具有自动存储持续时间)以封闭 block 结束,之后任何引用它的尝试(通过用于指向对象的指针)都是未定义的,不需要诊断。
在第二个代码片段中,不仅在语法上可以在 if
block 之后使用 d
,它还被定义为访问表示的对象。
为了说明标识符的范围和所表示对象的生命周期之间的区别,这是有效的 C99(和 C11)代码:
void foo(void) {
int *p = 0;
again:
if(p) {
printf("%d\n", *p); /* n is not in scope here, but the object exists */
*p = 0;
}
int n = 42;
printf("%d\n", n);
if(!p) {
p = &n;
goto again;
}
}
输出三次42
,当第二次到达初始化器时,n
被重新初始化为42(并且不停留为0)。
C89 不会出现这样的问题(标签不能在声明之上);在 GNU89 中,混合声明和代码是允许的,尽管我从 documentation 中不清楚如果保证遵守 C99 生命周期规则。
此代码未定义(在所有 C 标准中):
void foo(void) {
int *p = 0;
for(int i=0; i<2; ++i) {
int n = 42;
if(p) { /* (*) */
printf("%d\n", *p);
}
p = &n;
}
}
在第二次迭代中,p
指的是第一次迭代的 n
,在其生命周期之后,尽管 n
可能都位于相同的存储位置,并输出 42
。注意,当 (*)
第二次到达时,行为是未定义的,读取无效指针是未定义的,不仅是 printf
调用中的间接寻址。
关于c - 在 if block 中声明变量的确切效果,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29454271/