【问题标题】:Exact effect of declaring variable inside if block在 if 块中声明变量的确切效果
【发布时间】:2015-06-09 20:19:50
【问题描述】:

我很想了解 C 中以下内容的效果:

int func(int arg) {
    if (arg == 0) {
        double *d = malloc(...);
    }
    //...
}

我的理解是:

  • 无论arg 的值如何,调用func 时都会为指针d 腾出堆栈空间
  • d 仅被初始化,即 malloc 被调用,如果 arg == 0
  • d 只能在 if 块内部访问;尝试在外部访问它会产生一个编译错误 - 即使d 的堆栈空间无论如何都已分配。

所以,除了阻止访问 if 块之外的范围规则之外,它等价于以下内容:

int func(int arg) {
    double *d;
    if (arg == 0) {
        d = malloc(...);
    }
    //...
}

这是正确的吗?我正在使用icc 默认设置进行编译,这似乎是std=gnu89

【问题讨论】:

  • 第一点可能不正确。可能依赖于编译器。
  • 甚至不必有 any 堆栈。 C 不强制要求堆栈,即使使用堆栈,编译器也可以将变量保存在寄存器中。

标签: c scope icc


【解决方案1】:

d 表示的对象的生命周期从声明它的块的开头开始(可能在声明之前),不一定在函数的开头。在实践中,编译器可能会选择在函数入口处为所有变量分配空间;例如,Gcc 将func 的两个版本编译为相同的程序集。一个函数中只有几个自动变量,很可能它们都放在寄存器中,根本没有使用堆栈空间。

初始化发生在初始化器出现的地方。所有这一切都受制于 as-if 规则(一如既往):在这种情况下,Gcc 在优化时不会生成任何对 malloc 的调用(从而消除内存泄漏),允许编译器“知道”什么标准库函数可以。如果这不是库函数并且编译器不知道定义,则保证在到达初始化程序时准确地发生调用。

使用未声明的标识符(或超出范围的标识符)是一种语法错误,因此会在编译时被捕获。表示对象的生命周期(具有自动存储持续时间)以封闭块结束,之后任何引用它的尝试(通过用于指向对象的指针)都是未定义的,不需要诊断。

在第二个代码 sn-p 中,不仅在语法上可以在 if 块之后使用 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 调用中的间接。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-20
    • 1970-01-01
    • 2016-06-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-18
    相关资源
    最近更新 更多