简体   繁体   English

C 中的变量声明位置

[英]Variable declaration placement in C

I long thought that in C, all variables had to be declared at the beginning of the function.我一直认为在 C 中,所有变量都必须在函数的开头声明。 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?我知道在C99中,规则和C++一样,但是C89/ANSI C的变量声明放置规则是什么?

The following code compiles successfully with gcc -std=c89 and gcc -ansi :以下代码使用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;
}

Shouldn't the declarations of c and s cause an error in C89/ANSI mode? cs的声明不应该在 C89/ANSI 模式下导致错误吗?

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.它编译成功是因为 GCC 允许将s声明为 GNU 扩展,即使它不是 C89 或 ANSI 标准的一部分。 If you want to adhere strictly to those standards, you must pass the -pedantic flag.如果你想严格遵守这些标准,你必须通过-pedantic标志。

The declaration of c at the start of a { } block is part of the C89 standard; { }块开头的c声明是 C89 标准的一部分; the block doesn't have to be a function.该块不必是一个函数。

For C89, you must declare all of your variables at the beginning of a scope block .对于 C89,您必须在作用域块的开头声明所有变量。

So, your char c declaration is valid as it is at the top of the for loop scope block.因此,您的char c声明是有效的,因为它位于 for 循环范围块的顶部。 But, the char *s declaration should be an error.但是, char *s声明应该是一个错误。

Grouping variable declarations at the top of the block is a legacy likely due to limitations of old, primitive C compilers.由于旧的、原始的 C 编译器的限制,在块的顶部对变量声明进行分组是一种遗留问题。 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.分离声明和初始化还可以防止您尽可能使用“const”(或“final”)。

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:不幸的是,C++ 一直接受旧的、顶级的声明方式以与 C 向后兼容(一个 C 兼容性拖出了许多其他人......)但 C++ 试图摆脱它:

  • The design of C++ references does not even allow such top of the block grouping. C++ 引用的设计甚至不允许这种块分组的顶部。
  • If you separate declaration and initialization of a C++ local object then you pay the cost of an extra constructor for nothing.如果您将 C++ 本地对象的声明和初始化分开,那么您将无偿支付额外构造函数的成本。 If the no-arg constructor does not exist then again you are not even allowed to separate both!如果 no-arg 构造函数不存在,那么您甚至不允许将两者分开!

C99 starts to move C in this same direction. C99 开始在同一个方向上移动 C。

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 https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions

From a maintainability, rather than syntactic, standpoint, there are at least three trains of thought:从可维护性而不是语法的角度来看,至少有三种思路:

  1. 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.在函数的开头声明所有变量,这样它们就在一个地方,您可以一目了然地看到完整的列表。

  2. Declare all variables as close as possible to the place they're first used, so you'll know why each is needed.将所有变量声明为尽可能靠近它们首次使用的位置,这样您就会知道为什么需要每个变量。

  3. 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.一个例子可能是在fork() ,声明只有子进程需要的变量。 To me, this visual indicator is a helpful reminder of their purpose.对我来说,这个视觉指示器有助于提醒他们的目的。

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.正如其他人所指出的,GCC 在这方面是宽容的(可能还有其他编译器,取决于它们被调用的参数),即使在“C89”模式下,除非您使用“迂腐”检查。 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. C89 要求在每个范围内的任何其他语句之前声明变量,后来的标准允许更接近使用的声明(这可以更直观和更有效),尤其是在“for”循环中同时声明和初始化循环控制变量。

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. 1) 在函数顶部声明所有内容,因为年份是 1987。

2) Declare closest to first use and in the smallest scope possible. 2) 声明最接近首次使用并在尽可能小的范围内。

My answer to this is DO BOTH!我的答案是两者都做! Let me explain:让我解释:

For long functions, 1) makes refactoring very hard.对于长函数,1) 使得重构非常困难。 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.如果您在开发人员反对子例程概念的代码库中工作,那么您将在函数的开头有 50 个变量声明,其中一些可能只是 for 循环的“i”函数的底部。

I therefore developed declaration-at-the-top-PTSD from this and tried to do option 2) religiously.因此,我由此制定了最高声明 PTSD,并尝试虔诚地做选项 2)。

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

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.对所有局部变量执行此操作。

int foo = 0;
<code that uses foo>

int bar = 1;
<code that uses bar>

<code that uses foo>

Now, define a scope block that starts before the declaration and move the end until the program compiles现在,定义一个范围块,它在声明之前开始并移动到程序编译结束

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

int bar = 1;
<code that uses bar>

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

This doesn't compile because there is some more code that uses foo.这不会编译,因为还有一些使用 foo 的代码。 We can notice that the compiler was able to go through the code that uses bar because it doesn't use foo.我们可以注意到编译器能够通过使用 bar 的代码,因为它没有使用 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:机械的一种是将“}”向下移动直到它编译,另一种选择是检查代码并确定顺序是否可以更改为:

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

<code that uses foo>

int bar = 1;
<code that uses bar>

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.需要注意的另一件事是,是否需要在使用它的代码块之间保留 foo 的值,或者它是否可以在两者中使用不同的 foo。 For example例如

int i;

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

<some stuff>

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

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.如果第一次使用是在 if 语句中,将它放在那里并检查它是否编译。 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).如果在大范围块中使用了变量,请分析情况并查看您是否只是将同一个变量用于两个不同的事物(例如用于两个 for 循环的“i”)。 If the uses are unrelated, create new variables for each of these unrelated uses.如果用途不相关,则为这些不相关的用途中的每一个创建新变量。

I will quote some statements from the manual for gcc version 4.7.0 for a clear explanation.我将引用 gcc 4.7.0 版手册中的一些陈述以获得清晰的解释。

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

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.我认为你的问题的关键是,即使使用了选项“-std=c89”,为什么 gcc 不符合 C89。 I don't know the version of your gcc, but I think that there won't be big difference.我不知道你的 gcc 的版本,但我认为不会有太大的区别。 The developer of gcc has told us that the option "-std=c89" just means the extensions which contradict C89 are turned off. gcc 的开发人员告诉我们,选项“-std=c89”只是意味着与 C89 相矛盾的扩展被关闭。 So, it has nothing to do with some extensions that do not have a meaning in C89.因此,它与某些在 C89 中没有意义的扩展无关。 And the extension that don't restrict the placement of variable declaration belongs to the extensions that do not contradict C89.并且不限制变量声明放置的扩展属于与C89不矛盾的扩展。

To be honest, everyone will think that it should conform C89 totally at the first sight of the option "-std=c89".老实说,大家第一眼看到“-std=c89”这个选项都会认为它应该完全符合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.至于一开始就声明所有变量是好是坏的问题只是习惯问题。

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...). 1/ 嵌入式系统(特别是与飞机或汽车之类的生活相关):它确实允许您使用动态内存(例如:calloc、malloc、new...)。 Imagine you are working in a very big project, with 1000 engineers.想象一下,你正在一个非常大的项目中工作,有 1000 名工程师。 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)?如果飞机在 30 天内运行并且没有关闭,如果软件损坏(当飞机仍在空中时)会发生什么?

2/ The others system like web, PC (have large memory space): 2/ 其他系统如web、PC(内存空间大):

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只需做简单的事情即可重置 PC :P 对生活没有影响

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM