繁体   English   中英

C 中的变量声明位置

[英]Variable declaration placement in 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 模式下导致错误吗?

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

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

对于 C89,您必须在作用域块的开头声明所有变量。

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

由于旧的、原始的 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

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

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

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

  3. 在最内层作用域块的开始处声明所有变量,这样它们将尽快超出作用域,并允许编译器优化内存并告诉您是否在您不想要的地方意外使用它们。

我通常更喜欢第一个选项,因为我发现其他选项经常迫使我搜索声明的代码。 预先定义所有变量还可以更轻松地初始化和从调试器观察它们。

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

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

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

如前所述,对此有两种思想流派。

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

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

我的答案是两者都做! 让我解释:

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

我将引用 gcc 4.7.0 版手册中的一些陈述以获得清晰的解释。

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

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

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

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

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

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

如果飞机在 30 天内运行并且没有关闭,如果软件损坏(当飞机仍在空中时)会发生什么?

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

您应该“本地”声明变量以优化使用的内存。 如果这些系统运行时间长了会发生堆栈溢出(因为有人忘记移除动态内存)。 只需做简单的事情即可重置 PC :P 对生活没有影响

暂无
暂无

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

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