C 语言中的变量声明位置

我一直认为在 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 模式下导致错误?

101452 次浏览

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

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

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

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

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

  1. 在函数的开头声明所有的变量,这样它们就会在一个地方,你可以一眼看到全面的列表。

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

  3. 在最里面的作用域块的开头声明所有变量,这样它们就会尽快超出作用域,并允许编译器优化内存,如果您不小心在不希望的地方使用了它们,编译器就会告诉您。

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

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

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

不幸的是,c + + 继续采用旧的、顶级的声明方式来与 c 进行向下兼容(一个 c 兼容性拖出了许多其他的...)但是 c + + 试图摆脱它:

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

C99开始向这个方向移动 C。

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

Https://wiki.sei.cmu.edu/confluence/display/c/dcl19-c.+minimize+the+scope+of+variables+and+functions

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

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

我将引用 gcc 4.7.0版手册中的一些说明,以便做出清晰的解释。

“编译器可以接受几个基本标准,比如‘ c90’或‘ c + + 98’,以及这些标准的 GNU 方言,比如‘ gnu90’或‘ GNU + + 98’。通过指定一个基本标准,编译器将接受所有遵循该标准的程序,以及那些使用 GNU 扩展的程序,这些程序不会与该标准相抵触。例如,‘-std = C90’关闭了 GCC 中某些与 ISO C90不兼容的特性,比如 asm 和 typeof 关键字,但是没有关闭其他在 ISO C90中没有意义的 GNU 扩展,比如省略了 a?表情

我认为您的问题的关键在于,即使使用了选项“-std = C89”,为什么 gcc 不符合 C89。我不知道你的 GCC 是什么版本,但我觉得不会有太大区别。Gcc 的开发人员告诉我们,选项“-std = C89”只是意味着关闭了与 C89相矛盾的扩展。因此,它与一些在 C89中没有意义的扩展没有任何关系。而不限制变量声明位置的扩展属于与 C89不矛盾的扩展。

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

正如已经指出的那样,在这个问题上有两派观点。

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

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

我的回答是两者兼顾! 让我解释一下:

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

因此,我从中发展出了“创伤后应激障碍最高声明”,并试图虔诚地做出选择2。

我又回到了选项一,因为一件事: 短函数。如果函数足够短,那么本地变量就会很少,而且因为函数很短,所以如果将它们放在函数的顶部,它们仍然会接近第一次使用时的值。

此外,当您想要在顶部声明但是没有进行初始化所需的一些计算时,可以解决“ Declaration and set to 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 语句中,将其放在那里并检查它是否编译。然后,编译器将标识其他用途。尝试创建一个包含这两种用法的范围块。

这个机械部分完成后,就更容易分析数据在哪里。如果一个变量被用在一个大的作用域块中,分析一下情况,看看你是否只是在两个不同的事情上使用同一个变量(比如一个“ i”用于两个 for 循环)。如果使用是不相关的,则为每个不相关的使用创建新变量。

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

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

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

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

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

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