C 中变量声明的位置
我一直认为在 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< /code> 在 C89/ANSI 模式下导致错误?
I long thought that in C, all variables had to be declared at the beginning of the function. I know that in C99, the rules are the same as in C++, but what are the variable declaration placement rules for C89/ANSI C?
The following code compiles successfully with gcc -std=c89
and 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;
}
Shouldn't the declarations of c
and s
cause an error in C89/ANSI mode?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
它可以成功编译,因为 GCC 允许将
s
声明为 GNU 扩展,即使它不是 C89 或 ANSI 标准的一部分。 如果您想严格遵守这些标准,则必须传递-pedantic
标志。{ }
块开头的c
声明是 C89 标准的一部分; 该块不一定是一个函数。It compiles successfully because GCC allows the declaration of
s
as a GNU extension, even though it's not part of the C89 or ANSI standard. If you want to adhere strictly to those standards, you must pass the-pedantic
flag.The declaration of
c
at the start of a{ }
block is part of the C89 standard; the block doesn't have to be a function.对于 C89,您必须在范围块的开头声明所有变量。
因此,您的 char c 声明是有效的,因为它位于 for 循环作用域块的顶部。 但是,
char *s
声明应该是一个错误。For C89, you must declare all of your variables at the beginning of a scope block.
So, your
char c
declaration is valid as it is at the top of the for loop scope block. But, thechar *s
declaration should be an error.在块顶部对变量声明进行分组是一个遗留问题,可能是由于旧的原始 C 编译器的限制。 所有现代语言都建议甚至有时强制在最近的时间点声明局部变量:即它们首次初始化的位置。 因为这消除了错误使用随机值的风险。 分离声明和初始化还可以防止您在可以时使用“const”(或“final”)。
不幸的是,C++ 仍然接受旧的、顶级声明方式来向后兼容 C(其中一个 C 兼容性拖累了许多其他兼容性......)但 C++ 试图摆脱它:
C99 开始朝同一方向移动 C。
如果您担心找不到声明局部变量的位置,那么这意味着您遇到了一个更大的问题:封闭块太长,应该拆分。
https: //wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+最小化+变量+和+函数的+范围
Grouping variable declarations at the top of the block is a legacy likely due to limitations of old, primitive C compilers. All modern languages recommend and sometimes even enforce the declaration of local variables at the latest point: where they're first initialized. Because this gets rid of the risk of using a random value by mistake. Separating declaration and initialization also prevents you from using "const" (or "final") when you could.
C++ unfortunately keeps accepting the old, top declaration way for backward compatibility with C (one C compatibility drag out of many others...) But C++ tries to move away from it:
C99 starts to move C in this same direction.
If you are worried of not finding where local variables are declared then it means you have a much bigger problem: the enclosing block is too long and should be split.
https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions
从可维护性而不是语法的角度来看,至少有三种思路:
在函数的开头声明所有变量,以便它们位于一个位置,并且您将能够看到完整的列表一目了然。
在尽可能靠近首次使用的位置声明所有变量,这样您就会知道为什么需要每个变量。
在尽可能靠近首次使用的
在最里面的作用域块的开头声明所有变量,这样它们就会尽快超出作用域,并允许编译器优化内存,并告诉您是否不小心在不想要的地方使用了它们。< /p>
我通常更喜欢第一个选项,因为我发现其他选项经常迫使我在代码中寻找声明。 预先定义所有变量还可以更轻松地从调试器中初始化和观察它们。
有时我会在较小的作用域块内声明变量,但只是为了一个好的理由,而我的理由很少。 一个示例可能是在
fork()
之后,声明仅子进程所需的变量。 对我来说,这个视觉指示器有助于提醒他们的目的。From a maintainability, rather than syntactic, standpoint, there are at least three trains of thought:
Declare all variables at the beginning of the function so they'll be in one place and you'll be able to see the comprehensive list at a glance.
Declare all variables as close as possible to the place they're first used, so you'll know why each is needed.
Declare all variables at the beginning of the innermost scope block, so they'll go out of scope as soon as possible and allow the compiler to optimize memory and tell you if you accidentally use them where you hadn't intended.
I generally prefer the first option, as I find the others often force me to hunt through code for the declarations. Defining all variables up front also makes it easier to initialize and watch them from a debugger.
I'll sometimes declare variables within a smaller scope block, but only for a Good Reason, of which I have very few. One example might be after a
fork()
, to declare variables needed only by the child process. To me, this visual indicator is a helpful reminder of their purpose.正如其他人所指出的,即使在“C89”模式下,GCC 在这方面也是允许的(可能还有其他编译器,具体取决于它们调用的参数),除非您使用“迂腐”检查。 说实话,没有太多理由不迂腐。 高质量的现代代码应该总是在没有警告的情况下进行编译(或者很少有你知道你正在做一些特定的事情,而编译器可能会怀疑这是一个可能的错误),所以如果你不能让你的代码使用迂腐的设置进行编译,它可能需要一些注意。
C89 要求在每个作用域内的任何其他语句之前声明变量,后来的标准允许声明更接近使用(这可以更直观且更高效),特别是在“for”循环中同时声明和初始化循环控制变量。
As noted by others, GCC is permissive in this regard (and possibly other compilers, depending on the arguments they're called with) even when in 'C89' mode, unless you use 'pedantic' checking. To be honest, there are not many good reasons to not have pedantic on; quality modern code should always compile without warnings (or very few where you know you are doing something specific that is suspicious to the compiler as a possible mistake), so if you cannot make your code compile with a pedantic setup it probably needs some attention.
C89 requires that variables be declared before any other statements within each scope, later standards permit declaration closer to use (which can be both more intuitive and more efficient), especially the simultaneous declaration and initialization of a loop control variable in 'for' loops.
正如已经指出的,对此有两种思想流派。
1) 在函数顶部声明所有内容,因为年份是 1987 年。
2) 在最接近首次使用的情况下并在尽可能小的范围内声明。
我对此的回答是两者都做! 让我解释一下:
对于长函数,1)使得重构非常困难。 如果您在开发人员反对子例程的想法的代码库中工作,那么您将在函数开头有 50 个变量声明,其中一些可能只是代表 for 循环的“i”,该循环位于最开始处。函数的底部。
因此,我由此发展了“顶部声明”PTSD,并尝试虔诚地执行选项 2)。
我回到选项一是因为一件事:函数短。 如果你的函数足够短,那么你将有很少的局部变量,并且由于函数很短,如果你将它们放在函数的顶部,它们仍然会接近第一次使用。
此外,当您想在顶部声明但尚未进行一些初始化所需的计算时,“声明并设置为 NULL”的反模式已得到解决,因为您需要初始化的内容可能会作为参数接收。
所以现在我的想法是你应该在函数的顶部声明并尽可能接近第一次使用。 所以两者都是! 做到这一点的方法是使用划分良好的子例程。
但是,如果您正在处理一个很长的函数,那么请将最接近第一次使用的内容放在一起,因为这样会更容易提取方法。
我的食谱是这样的。 对于所有局部变量,获取变量并将其声明移动到底部,编译,然后将声明移动到编译错误之前。 这是第一次使用。 对所有局部变量执行此操作。
现在,定义一个范围块,该范围块在声明之前开始,并移动末尾直到程序编译。
这不会编译,因为还有一些使用 foo 的代码。 我们可以注意到,编译器能够遍历使用 bar 的代码,因为它不使用 foo。 此时,有两个选择。 机械的一个是将“}”向下移动直到编译,另一种选择是检查代码并确定顺序是否可以更改为:
如果顺序可以切换,这可能就是您想要的,因为它缩短了临时值的生命周期。
另一件需要注意的事情是, foo 的值是否需要在使用它的代码块之间保留,或者它可能只是两个代码块中不同的 foo 。 例如
这些情况需要的不仅仅是我的程序。 开发人员必须分析代码以确定要做什么。
但第一步是找到第一个用途。 您可以直观地完成此操作,但有时,删除声明、尝试编译并将其放回第一次使用之上会更容易。 如果第一次使用是在 if 语句内,请将其放在那里并检查它是否可以编译。 然后编译器将识别其他用途。 尝试创建一个包含这两种用途的范围块。
完成这个机械部分后,分析数据在哪里就变得更容易了。 如果在大作用域块中使用变量,请分析情况并查看是否只是将同一个变量用于两个不同的事物(例如用于两个 for 循环的“i”)。 如果用途不相关,请为每个不相关的用途创建新变量。
As has been noted, there are two schools of thought on this.
1) Declare everything at the top of functions because the year is 1987.
2) Declare closest to first use and in the smallest scope possible.
My answer to this is DO BOTH! Let me explain:
For long functions, 1) makes refactoring very hard. If you work in a codebase where the developers are against the idea of subroutines, then you'll have 50 variable declarations at the start of the function and some of them might just be an "i" for a for-loop that's at the very bottom of the function.
I therefore developed declaration-at-the-top-PTSD from this and tried to do option 2) religiously.
I came back around to option one because of one thing: short functions. If your functions are short enough, then you will have few local variables and since the function is short, if you put them at the top of the function, they will still be close to the first use.
Also, the anti-pattern of "declare and set to NULL" when you want to declare at the top but you haven't made some calculations necessary for initialization is resolved because the things you need to initialize will likely be received as arguments.
So now my thinking is that you should declare at the top of functions and as close as possible to first use. So BOTH! And the way to do that is with well divided subroutines.
But if you're working on a long function, then put things closest to first use because that way it will be easier to extract methods.
My recipe is this. For all local variables, take the variable and move it's declaration to the bottom, compile, then move the declaration to just before the compilation error. That's the first use. Do this for all local variables.
Now, define a scope block that starts before the declaration and move the end until the program compiles
This doesn't compile because there is some more code that uses foo. We can notice that the compiler was able to go through the code that uses bar because it doesn't use foo. At this point, there are two choices. The mechanical one is to just move the "}" downwards until it compiles, and the other choice is to inspect the code and determine if the order can be changed to:
If the order can be switched, that's probably what you want because it shortens the lifespan of temporary values.
Another thing to note, does the value of foo need to be preserved between the blocks of code that use it, or could it just be a different foo in both. For example
These situations need more than my procedure. The developer will have to analyse the code to determine what to do.
But the first step is finding the first use. You can do it visually but sometimes, it's just easier to delete the declaration, try to compile and just put it back above the first use. If that first use is inside an if statement, put it there and check if it compiles. The compiler will then identify other uses. Try to make a scope block that encompasses both uses.
After this mechanical part is done, then it becomes easier to analyse where the data is. If a variable is used in a big scope block, analyse the situation and see if you're just using the same variable for two different things (like an "i" that gets used for two for loops). If the uses are unrelated, create new variables for each of these unrelated uses.
我将引用 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。 但事实并非如此。
至于一开始就声明所有变量是好还是坏的问题只是一个习惯问题。
I will quote some statements from the manual for gcc version 4.7.0 for a clear explanation.
"The compiler can accept several base standards, such as ‘c90’ or ‘c++98’, and GNU dialects of those standards, such as ‘gnu90’ or ‘gnu++98’. By specifying a base standard, the compiler will accept all programs following that standard and those using GNU extensions that do not contradict it. For example, ‘-std=c90’ turns off certain features of GCC that are incompatible with ISO C90, such as the asm and typeof keywords, but not other GNU extensions that do not have a meaning in ISO C90, such as omitting the middle term of a ?: expression."
I think the key point of your question is that why does not gcc conform to C89 even if the option "-std=c89" is used. I don't know the version of your gcc, but I think that there won't be big difference. The developer of gcc has told us that the option "-std=c89" just means the extensions which contradict C89 are turned off. So, it has nothing to do with some extensions that do not have a meaning in C89. And the extension that don't restrict the placement of variable declaration belongs to the extensions that do not contradict C89.
To be honest, everyone will think that it should conform C89 totally at the first sight of the option "-std=c89". But it doesn't.
As for the problem that declare all variables at the beginning is better or worse is just A matter of habit.
您应该在函数的顶部或“本地”声明所有变量。 答案是:
这取决于您使用的系统类型:
1/嵌入式系统(特别是与飞机或汽车等生活相关的系统):
它确实允许您使用动态内存(例如:calloc、malloc、new...)。 想象一下您正在从事一个非常大的项目,有 1000 名工程师。 如果他们分配新的动态内存并忘记删除它(当它不再使用时)怎么办? 嵌入式系统如果长时间运行,就会导致堆栈溢出,软件崩溃。 质量不容易保证(最好的办法是禁止动态内存)。
如果一架飞机运行了 30 天并且没有关闭,如果软件损坏(当飞机仍在空中时)会发生什么?
2/ 其他系统,如网络、PC(具有较大内存空间):
您应该“本地”声明变量以优化内存使用。 如果这些系统运行很长时间并且发生堆栈溢出(因为有人忘记删除动态内存)。 只需执行简单的操作即可重置电脑:P 它对生活没有影响
You should declare all variable at the top or "locally" in the function. The answer is:
It depends on what kind you system you are using:
1/ Embedded System (especially related to lives like Airplane or Car):
It does allow you to use dynamic memory (eg: calloc, malloc, new...). Imagine you are working in a very big project, with 1000 engineers. What if they allocate new dynamic memory and forgot to remove it (when it does not use anymore)? If the embedded system run for a long time, it will lead to stack overflow and software will corrupt. Not easy to make sure the quality (the best way is ban dynamic memory).
If an Airplane run in 30days and doesnot turnoff, what happens if software is corrupted (when the airplane still in the air)?
2/ The others system like web, PC (have large memory space):
You should declare variable "locally" to optimize the memory using. If these system run for a long time and stack overflow happen (because someone forgot to remove dynamic memory). Just do the simple thing to reset the PC :P Its no impact on lives