C中的变量声明放置

时间:2008-11-13 21:43:37

标签: c declaration c89

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

以下代码与gcc -std=c89gcc -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;
}

cs的声明是否应该在C89 / ANSI模式下导致错误?

7 个答案:

答案 0 :(得分:134)

它成功编译,因为GCC允许它作为GNU扩展,即使它不是C89或ANSI标准的一部分。如果您想严格遵守这些标准,则必须传递-pedantic标志。

答案 1 :(得分:72)

对于C89,您必须在范围块的开头声明所有变量。

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

答案 2 :(得分:27)

在块的顶部对变量声明进行分组是遗留问题,可能是由于旧的原始C编译器的限制。所有现代语言都推荐甚至有时甚至强制在最新点声明局部变量:它们首先被初始化。因为这样可以避免错误地使用随机值的风险。分离声明和初始化也会阻止你在可能时使用“const”(或“final”)。

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

  • C ++引用的设计甚至不允许这样的块分组。
  • 如果您将C ++本地对象的声明和初始化分开,那么您无需支付额外构造函数的成本。如果no-arg构造函数不存在,那么你甚至不能再将它们分开!

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

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

https://www.securecoding.cert.org/confluence/display/cplusplus/DCL19-CPP.+Initialize+automatic+local+variables+on+declaration

答案 3 :(得分:23)

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

  1. 在函数开头声明所有变量,这样它们就会在一个地方,你就可以一目了然地看到综合列表。

  2. 声明所有变量尽可能接近他们最初使用的地方,因此您将知道为什么需要每个变量。

  3. 在最里面的范围块的开头声明所有变量,因此它们将尽快超出范围并允许编译器优化内存并告诉您是否在不使用的情况下意外使用它们意图。

  4. 我通常更喜欢第一个选项,因为我发现其他人经常强迫我通过代码搜索声明。预先定义所有变量也可以更容易地从调试器初始化和观察它们。

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

答案 4 :(得分:6)

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

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

答案 5 :(得分:0)

正如已经指出的那样,有两种思想流派。

1)声明一切都在函数顶部,因为这一年是1987年。

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

我对此的回答是DO BOTH!让我解释一下:

对于长函数,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”)。如果用途不相关,则为每个不相关的用途创建新变量。

答案 6 :(得分:-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。但事实并非如此。 至于在开始时声明所有变量更好或更差的问题仅仅是习惯问题。