在块中声明变量的确切效果

时间:2015-04-05 05:01:18

标签: c scope icc

我想要了解以下内容在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

1 个答案:

答案 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调用中的间接。