哪里可以而且不能在C中声明新变量?

时间:2011-12-12 12:10:39

标签: c declaration

我听说(可能来自老师)应该在程序/函数之上声明所有变量,并且在语句中声明新变量可能会导致问题。

但后来我正在阅读K& R并且我遇到了这句话:“变量的声明(包括初始化)可能会在左括号后面引入任何复合语句,而不仅仅是开始函数的语句”。他举了一个例子:

if (n > 0){
    int i;
    for (i=0;i<n;i++)
    ...
}

我使用了这个概念,它甚至可以用于数组。例如:

int main(){
    int x = 0 ;

    while (x<10){
        if (x>5){
            int y[x];
            y[0] = 10;
            printf("%d %d\n",y[0],y[4]);
        }
        x++;
    }
}

那么当我不允许声明变量时呢?例如,如果我的变量声明不在开括号之后呢?像这里:

int main(){
    int x = 10;

    x++;
    printf("%d\n",x);

    int z = 6;
    printf("%d\n",z);
}

根据程序/机器的不同,这会引起麻烦吗?

7 个答案:

答案 0 :(得分:109)

我也经常听到将变量置于函数顶部是做事的最佳方式,但我强烈反对。我更喜欢将变量限制在可能的最小范围内,因此它们被滥用的机会较少,因此我在程序的每一行中填充心理空间的东西较少。

虽然所有版本的C都允许使用词汇块范围,但您可以声明变量取决于您所定位的C标准版本:

C99起或C ++

现代C编译器(如gcc和clang)支持C99C11标准,这些标准允许您在语句的任何位置声明变量。变量的范围从声明点开始到块的结尾(下一个右括号)。

if( x < 10 ){
   printf("%d", 17);  // z is not in scope in this line
   int z = 42;
   printf("%d", z);   // z is in scope in this line
}

您还可以在for循环初始化器中声明变量。变量只存在于循环内部。

for(int i=0; i<10; i++){
    printf("%d", i);
}

ANSI C(C90)

如果您的目标是较旧的ANSI C标准,那么您只能在开括号 1 之后立即声明变量。

这并不意味着您必须在函数顶部声明所有变量。在C语言中,你可以在一个语句的任何地方放置一个大括号分隔的块(不仅仅是在iffor之后),你可以使用它来引入新的变量作用域。以下是以前C99示例的ANSI C版本:

if( x < 10 ){
   printf("%d", 17);  // z is not in scope in this line

   {
       int z = 42;
       printf("%d", z);   // z is in scope in this line
   }
}

{int i; for(i=0; i<10; i++){
    printf("%d", i);
}}

1 请注意,如果您使用的是gcc,则需要传递--pedantic标志以使其实际执行C90标准并抱怨变量声明的位置错误。如果你只使用-std=c90,它会使gcc接受C90的超集,这也允许更灵活的C99变量声明。

答案 1 :(得分:4)

missingno涵盖ANSI C允许的内容,但他没有解释为什么你的老师告诉你在你的函数顶部声明你的变量。在奇数位置声明变量会使代码更难以阅读,这可能会导致错误。

以下面的代码为例。

#include <stdio.h>

int main() {
    int i, j;
    i = 20;
    j = 30;

    printf("(1) i: %d, j: %d\n", i, j);

    {
        int i;
        i = 88;
        j = 99;
        printf("(2) i: %d, j: %d\n", i, j);
    }

    printf("(3) i: %d, j: %d\n", i, j);

    return 0;
}

如您所见,我已经宣布i两次。好吧,更确切地说,我已经声明了两个名为i的变量。您可能认为这会导致错误,但事实并非如此,因为两个i变量位于不同的范围内。当您查看此函数的输出时,您可以更清楚地看到这一点。

(1) i: 20, j: 30
(2) i: 88, j: 99
(3) i: 20, j: 99

首先,我们分别为ij分配20和30。然后,在大括号内,我们分配88和99.那么,为什么j保持其值,但i又回到20?这是因为两个不同的i变量。

在内部花括号之间,i变量值为20隐藏且无法访问,但由于我们尚未声明新的j,我们仍在使用j来自外部范围。当我们离开内部花括号时,保持值88的i消失,我们再次可以访问值为20的i

有时这种行为是好事,有时可能不是,但应该很清楚,如果你不加区别地使用C的这个功能,你可以真正让你的代码混乱,难以理解。

答案 2 :(得分:1)

如果您的编译器允许它,那么可以在任何您想要的地方声明它。实际上,当您在使用的位置声明变量而不是在函数的顶部时,代码更具可读性(恕我直言),因为它可以更容易地发现错误,例如忘记初始化变量或意外隐藏变量。

答案 3 :(得分:1)

帖子显示以下代码:

//C99
printf("%d", 17);
int z=42;
printf("%d", z);

//ANSI C
printf("%d", 17);
{
    int z=42;
    printf("%d", z);
}

我认为这意味着它们是等价的。他们不是。如果int z位于此代码段的底部,则会导致针对第一个z定义的重定义错误,而不是针对第二个定义的重定义错误。

然而,多行:

//C99
for(int i=0; i<10; i++){}

确实有效。显示出这个C99规则的微妙之处。

就个人而言,我热情地避开了这个C99功能。

它缩小变量范围的论点是假的,如这些例子所示。在新规则下,在扫描整个块之前,您无法安全地声明变量,而以前只需要了解每个块头部的内容。

答案 4 :(得分:0)

按照K&amp; R的C编程语言 -

在C中,所有变量必须在使用之前声明,通常在 任何可执行语句之前的函数开头。

在这里你可以看到通常不是必须的单词..

答案 5 :(得分:0)

在使用clang和gcc时,我遇到了以下主要问题。 gcc版本8.2.1 20181011 clang版本6.0.1

  {
    char f1[]="This_is_part1 This_is_part2";
    char f2[64]; char f3[64];
    sscanf(f1,"%s %s",f2,f3);      //split part1 to f2, part2 to f3 
  }

任何一个编译器都不喜欢f1,f2或f3在该块内。我必须将f1,f2,f3移到函数定义区域。 编译器并不介意使用该块定义整数。

答案 6 :(得分:0)

内部所有函数局部变量都分配在堆栈上或内部CPU寄存器中,然后,如果编译器性能较差或CPU没有此功能,则生成的机器代码在寄存器和堆栈之间交换(称为寄存器溢出)足够的寄存器以保持所有球在空中飞奔。

要在堆栈上分配内容,CPU有两个特殊的寄存器,一个称为堆栈指针(SP),另一个称为基址指针(BP)或帧指针(表示当前函数作用域本地的堆栈帧)。 SP指向堆栈上当前位置的内部,而BP指向工作数据集(在其上方)和函数自变量(在其下方)。调用函数时,它将调用者/父函数的BP推入堆栈(由SP指向),并将当前SP设置为新的BP,然后将SP从寄存器溢出到堆栈的字节数增加,进行计算,并在返回时通过从堆栈中弹出来恢复其父级的BP。

通常,将变量保留在自己的{}范围内可以通过减小编译器确定用于何处以及如何使用哪些变量的图形的大小来加快编译速度并改善生成的代码。在某些情况下(尤其是在涉及goto的情况下),除非您明确告诉编译器其使用范围,否则编译器可能会错过不再使用该变量的事实。编译器可能有时间/深度限制来搜索程序图。

编译器可以将彼此声明的变量放在同一堆栈区域,这意味着加载一个会将所有其他变量预加载到缓存中。同样,声明变量register可以为编译器提供一个提示,即您希望避免不惜一切代价使该变量溢出到堆栈中。

严格的C99标准要求在声明之前使用显式{,而C ++和GCC引入的扩展允许在主体中进一步声明var,这使gotocase语句变得复杂。 C ++还允许在循环初始化中声明内部内容,这仅限于循环范围。

最后但并非最不重要的一点是,对于另一个正在阅读代码的人来说,当他看到一个函数的顶部充满了半百个变量声明,而不是将它们定位在使用位置时,这将是不知所措。它还使注释它们的使用更加容易。

TLDR:使用{}显式声明变量的作用域可以帮助编译器和人工阅读器。