我一直认为,在C,所有的变量必须在函数的开始申报。 我知道,在C99中,规则是一样的在C ++中,但什么是C89 / ANSI C变量声明放置规则?
下面的代码与编译成功gcc -std=c89
和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;
}
不应该的声明c
和s
造成C89 / ANSI模式错误?
它编译成功,因为GCC允许它作为一个GNU扩展,即使它不是C89或ANSI标准的一部分。 如果要严格遵守这些标准,你必须通过-pedantic
标志。
对于C89,你必须在一个范围块的开头声明所有的变量。
所以,你的char c
声明是有效的,因为它是在的顶部for循环的范围块。 但是,该char *s
的声明应该是一个错误。
在该块的顶分组变量声明为遗留可能是由于老的,原始的C编译器的限制。 所有现代语言建议,有时甚至在最新的点执行局部变量的声明:在那里他们首先初始化。 由于这种摆脱错误地使用随机值的风险。 分离声明和初始化也阻止您使用“常量”(或“最终”)时,你可以。
C ++不幸的是不断接受旧的,上面的声明方式为使用C向后兼容性(一个C兼容性拖出许多其他的...),但C ++试图远离它移动:
- 的C ++引用设计甚至不允许块分组的这样的顶部。
- 如果你单独的声明和C ++本地对象的初始化,然后你支付任何额外的构造成本。 如果无参数的构造函数不然后再存在,你甚至不能分离两个!
C99开始在这同一方向移动℃。
如果你担心找不到局部变量声明的话,那就意味着你有一个更大的问题:封闭块太长,应该被拆分。
https://www.securecoding.cert.org/confluence/display/cplusplus/DCL19-CPP.+Initialize+automatic+local+variables+on+declaration
从可维护性,而不是语法,观点来看,有思想的至少三列火车:
声明所有变量在函数的开头,以便他们将在一个地方,你就可以看到一目了然的完整列表。
声明尽可能接近到他们第一次使用的地方所有的变量,这样你就会知道为什么每个需要。
声明所有变量在最里面的范围块的开始,所以他们会尽快走出去的范围,让编译器优化内存,并告诉你,如果你不小心使用它们,你并没有打算。
我通常喜欢第一种选择,因为我发现其他人经常逼我通过代码打猎的声明。 定义了前面的所有变量也可以更容易地初始化和调试器观看。
我有时会一个较小的范围内块声明变量,但只是一个很好的理由,而我已经很少。 一个例子可能是一个后fork()
申报只能由子进程需要的变量。 对我来说,这可视指示器是他们的目的的有益的提醒。
正如其他人所指出的,GCC是宽容在这方面(可能还有其他的编译器,根据参数的他们是所谓的用),即使在“C89”模式时,除非你使用“迂腐”检查。 说实话,没有很多很好的理由没有迂腐的; 高品质的现代化的代码应该总是编译不(你知道你正在做的事情具体是可疑的编译器作为一个可能的错误或很少)警告,因此,如果你不能让你的代码编译它可能需要一些关注的迂腐设置。
C89要求变量声明的每个范围内的任何其它语句,后来的标准允许声明之前更接近使用(这既可以是更直观和更有效的),在“关于”环特别是同时的声明和一个循环控制变量的初始化。
正如已指出的,有这个想法的两所学校。
1)在函数的顶部声明一切,因为这一年是1987年。
2)最接近声明第一次使用和在最小范围内的可能。
我的答案是一举两得! 让我解释:
对于长功能,1),使重构很辛苦。 如果你在一个代码库,其中开发人员对子程序的思想工作,那么你将有50页变量的声明在函数的开始,其中一些可能只是一个“i”为一个for循环,就是在最该函数的底部。
因此,我开发了声明,在最顶级的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更多的代码。 我们可以看到,编译器能够去通过使用吧,因为它不使用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循环)。 如果用途是不相关的,请为每个无关用途的新变量。
我将引用从手动一些报表gcc版本4.7.0的一个明确的解释。
“编译器可以接受几个基本标准,如‘C90’或‘C ++ 98’,以及这些标准GNU方言,如‘gnu90’或‘GNU ++ 98’。通过指定基本标准,编译器将接受所有的程序以下这个标准,并使用GNU扩展那些不矛盾的。例如,“-std = C90”关闭那些不符合ISO C90 GCC的某些功能,如ASM和typeof运算的关键字,但不其他GNU扩展没有在ISO C90意义,如省略的中期:表达“。
我觉得你的问题的关键是,为什么不gcc的符合C89即使使用选项“-std = C89”。 我不知道你的gcc版本,但我认为不会有很大的区别。 GCC的开发者告诉我们,选择“-std = C89”仅仅是指矛盾C89的扩展被关闭。 因此,它没有任何关系了一些扩展,不必在C89的含义。 并且不限制变量声明的位置的分机属于不矛盾C89的扩展。
说实话,每个人都会认为它应该一见钟情选项“-std = C89”的完全符合C89。 但事实并非如此。 至于那个声明的所有变量的开头是好还是坏的问题仅仅是一个习惯问题。