140

我一直认为在 C 中,所有变量都必须在函数的开头声明。我知道在 C99 中,规则与 C++ 中相同,但 C89/ANSI C 的变量声明放置规则是什么?

以下代码使用gcc -std=c89and成功编译gcc -ansi

#include <stdio.h>
int main() {
    int i;
    for (i = 0; i < 10; i++) {
        char c = (i % 95) + 32;
        printf("%i: %c\n", i, c);
        char *s;
        s = "some string";
        puts(s);
    }
    return 0;
}

不应该在 C89/ANSI 模式下声明cs导致错误吗?

4

8 回答 8

165

它编译成功是因为 GCC 允许将 声明s为 GNU 扩展,即使它不是 C89 或 ANSI 标准的一部分。如果你想严格遵守这些标准,你必须通过-pedantic标志。

c块开头的声明{ }是 C89 标准的一部分;该块不必是一个函数。

于 2008-11-13T21:47:29.513 回答
79

对于 C89,您必须在作用域块的开头声明所有变量。

因此,您的char c声明是有效的,因为它位于 for 循环范围块的顶部。但是,char *s声明应该是一个错误。

于 2008-11-13T21:53:01.127 回答
45

由于旧的原始 C 编译器的限制,在块的顶部对变量声明进行分组是一种遗留问题。所有现代语言都建议,有时甚至强制在最晚点声明局部变量:它们首先被初始化的地方。因为这消除了错误使用随机值的风险。分离声明和初始化还可以防止您尽可能使用“const”(或“final”)。

不幸的是,C++ 一直接受旧的、顶级的声明方式来向后兼容 C(一个 C 兼容性拖累了许多其他的......)但 C++ 试图摆脱它:

  • C++ 引用的设计甚至不允许这样的块顶部分组。
  • 如果您将 C++ 本地对象的声明和初始化分开,那么您就无需支付额外构造函数的成本。如果无参数构造函数不存在,那么您甚至不允许将两者分开!

C99 开始向同一方向移动 C。

如果您担心找不到声明局部变量的位置,那么这意味着您有一个更大的问题:封闭块太长,应该拆分。

https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions

于 2010-11-05T11:11:33.847 回答
22

从可维护性而非句法的角度来看,至少有三种思路:

  1. 在函数的开头声明所有变量,这样它们就在一个地方,你就能一眼看到完整的列表。

  2. 将所有变量声明为尽可能靠近它们首次使用的位置,这样您就会知道为什么需要每个变量。

  3. 在最内层范围块的开头声明所有变量,以便它们尽快超出范围并允许编译器优化内存并告诉您是否不小心在不打算使用的地方使用了它们。

我通常更喜欢第一个选项,因为我发现其他选项经常迫使我在代码中寻找声明。预先定义所有变量还可以更容易地从调试器初始化和观察它们。

我有时会在较小的范围块内声明变量,但只是出于一个很好的理由,其中我很少。一个例子可能是在fork(), 之后声明只有子进程需要的变量。对我来说,这个视觉指示器有助于提醒他们的目的。

于 2008-11-13T22:05:49.820 回答
8

正如其他人所指出的,即使在“C89”模式下,GCC 在这方面也是允许的(可能还有其他编译器,取决于它们被调用的参数),除非您使用“pedantic”检查。老实说,没有太多好理由不迂腐;高质量的现代代码应该总是在没有警告的情况下编译(或者很少有人知道你正在做一些对编译器来说可能是可疑的特定错误),所以如果你不能用迂腐的设置来编译你的代码,它可能需要一些注意。

C89 要求在每个范围内的任何其他语句之前声明变量,后来的标准允许声明更接近使用(这可以更直观和更有效),特别是在“for”循环中同时声明和初始化循环控制变量。

于 2011-05-16T11:10:15.710 回答
1

如前所述,对此有两种思想流派。

1)在函数顶部声明所有内容,因为年份是 1987 年。

2) 声明最接近首次使用并尽可能在最小范围内。

我对此的回答是两者都做!让我解释:

对于长函数,1) 使重构非常困难。如果您在开发人员反对子例程概念的代码库中工作,那么您将在函数开头有 50 个变量声明,其中一些可能只是 for 循环的“i”函数的底部。

因此,我由此开发了最顶层的 PTSD 声明,并尝试虔诚地执行选项 2)。

我回到选项一是因为一件事:短函数。如果你的函数足够短,那么你的局部变量就会很少,而且由于函数很短,如果你把它们放在函数的顶部,它们仍然接近第一次使用。

此外,当您想在顶部声明但尚未进行初始化所需的一些计算时,“声明并设置为 NULL”的反模式已解决,因为您需要初始化的内容可能会作为参数接收。

所以现在我的想法是你应该在函数的顶部声明并尽可能接近第一次使用。所以两者都有!做到这一点的方法是使用划分良好的子程序。

但是如果你正在处理一个长函数,那么把最接近第一次使用的东西放在最接近的地方,因为这样会更容易提取方法。

我的食谱是这样的。对于所有局部变量,获取变量并将其声明移至底部,编译,然后将声明移至编译错误之前。这是第一次使用。对所有局部变量执行此操作。

int foo = 0;
<code that uses foo>

int bar = 1;
<code that uses bar>

<code that uses foo>

现在,定义一个在声明之前开始的范围块并移动结束直到程序编译

{
    int foo = 0;
    <code that uses foo>
}

int bar = 1;
<code that uses bar>

>>> First compilation error here
<code that uses foo>

这不会编译,因为还有一些使用 foo 的代码。我们可以注意到编译器能够通过使用 bar 的代码,因为它不使用 foo。此时,有两种选择。机械的一种是向下移动“}”直到它编译,另一种选择是检查代码并确定是否可以将顺序更改为:

{
    int foo = 0;
    <code that uses foo>
}

<code that uses foo>

int bar = 1;
<code that uses bar>

如果可以切换顺序,那可能就是您想要的,因为它会缩短临时值的寿命。

需要注意的另一件事是,是否需要在使用它的代码块之间保留 foo 的值,或者它可能只是两者中的不同 foo 。例如

int i;

for(i = 0; i < 8; ++i){
    ...
}

<some stuff>

for(i = 3; i < 32; ++i){
    ...
}

这些情况需要的不仅仅是我的程序。开发人员必须分析代码以确定要做什么。

但第一步是找到第一个用途。您可以直观地做到这一点,但有时,删除声明更容易,尝试编译并将其放回第一次使用之上。如果第一次使用在 if 语句中,请将其放在那里并检查它是否编译。然后编译器将识别其他用途。尝试制作一个包含两种用途的范围块。

完成这个机械部分后,分析数据的位置就变得更容易了。如果在大范围块中使用变量,请分析情况并查看您是否只是将相同的变量用于两个不同的事物(例如用于两个 for 循环的“i”)。如果用途不相关,则为这些不相关的用途中的每一个创建新变量。

于 2018-06-12T17:16:03.270 回答
-1

我将引用 gcc 4.7.0 版手册中的一些陈述以进行清晰的解释。

“编译器可以接受几个基本标准,例如'c90'或'c++98',以及这些标准的GNU方言,例如'gnu90'或'gnu++98'。通过指定一个基本标准,编译器将接受所有遵循该标准的程序以及那些使用与该标准不冲突的 GNU 扩展的程序。例如,'-std=c90' 会关闭与 ISO C90 不兼容的 GCC 的某些功能,例如 asm 和 typeof 关键字,但不其他在 ISO C90 中没有意义的 GNU 扩展,例如省略 ?: 表达式的中间项。”

我认为您问题的关键是即使使用了选项“-std=c89”,为什么 gcc 也不符合 C89。我不知道你的 gcc 的版本,但我认为不会有太大的不同。gcc 的开发者告诉我们,选项“-std=c89”只是意味着与 C89 相矛盾的扩展被关闭。因此,它与一些在 C89 中没有意义的扩展无关。并且不限制变量声明放置的扩展属于与C89不冲突的扩展。

老实说,大家第一眼看到“-std=c89”这个选项就会认为它应该完全符合C89。但事实并非如此。至于一开始就声明所有变量的问题是好是坏只是习惯问题。

于 2012-08-12T06:51:23.383 回答
-2

您应该在函数的顶部或“本地”声明所有变量。答案是:

这取决于您使用的系统类型:

1/ 嵌入式系统(特别是与飞机或汽车等生活相关的):它确实允许您使用动态内存(例如:calloc、malloc、new...)。想象一下,你在一个非常大的项目中工作,有 1000 名工程师。如果他们分配新的动态内存并忘记删除它(当它不再使用时)怎么办?如果嵌入式系统运行时间过长,会导致堆栈溢出,软件会损坏。不容易保证质量(最好的办法是禁止动态内存)。

如果一架飞机在 30 天内运行并且没有关闭,如果软件损坏(当飞机仍在空中)会发生什么?

2/其他系统如web、PC(有大内存空间):

您应该“本地”声明变量以优化内存使用。如果这些系统运行了很长时间并且发生堆栈溢出(因为有人忘记删除动态内存)。只需做简单的事情来重置电脑:P 它对生活没有影响

于 2020-01-20T07:52:23.200 回答