我想要了解以下内容在C中的效果:
int func(int arg) {
if (arg == 0) {
double *d = malloc(...);
}
//...
}
我的理解是:
arg
的值如何,当调用d
时,将为指针func
创建堆栈空间d
,则malloc
仅被初始化,即arg == 0
被调用
d
只能在if块内访问;尝试在外部访问它会产生编译错误 - 即使d
的堆栈空间是分配的。因此,除了阻止在if块之外访问的作用域规则之外,它等同于以下内容:
int func(int arg) {
double *d;
if (arg == 0) {
d = malloc(...);
}
//...
}
这是对的吗?我正在使用icc
默认设置进行编译,这些设置似乎是std=gnu89
。
答案 0 :(得分:1)
d
表示的对象的生命周期从声明它的块的开头(可能在声明之前)开始,不一定在函数的开头。实际上,编译器可以选择在函数入口处为所有变量分配空间;例如,Gcc将func
的两个版本编译为相同的程序集。由于函数中只有少数自动变量,它们可能都放在寄存器中,根本不会使用堆栈空间。
初始化发生在初始化程序出现的位置。所有这些都遵循as-if规则(一如既往):在这种情况下,Gcc在优化时不会生成对malloc
的任何调用(从而消除内存泄漏),允许编译器"知"标准库函数的作用。如果这不是库函数和编译器不知道的定义,则保证在完成初始化程序时确实发生调用。
使用未声明的标识符(或超出范围的标识符)是语法错误,因此在编译时捕获。表示对象的生命周期(具有自动存储持续时间)以封闭块结束,之后任何尝试引用它(通过用于指向对象的指针)都是未定义的,无需诊断。
在第二个代码段中,它不仅在语法上可以在d
块之后使用if
,它还被定义为访问所表示的对象。
为了说明标识符范围与所表示对象的生命周期之间的区别,这是有效的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中,允许使用混合声明和代码,但如果C99的终身规则得到保证,documentation对我来说并不清楚。
此代码未定义(在所有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
调用中的间接。