在C99中,我可以在哪里合法地声明变量?

40

我第一次接触 C 语言时被告知要在函数顶部声明变量。现在我对该语言有了较强的掌握,我正在关注编码风格,尤其是限制变量的作用域。我已经阅读了关于限制作用域的好处的文章,并遇到了一个有趣的例子。显然,C99 允许您这样做...

for (int i = 0; i < 10; i++)
{
   puts("hello");
}
我曾经认为,变量的作用域由最内层的“周围”大括号{ }限制,但在上面的例子中,即使它在大括号外声明,int i的作用域似乎也受到for循环的大括号的限制。
我尝试使用fgets()扩展上面的示例来执行我认为类似的操作,但这两个示例都给了我语法错误。
所以,在C99中到底哪里可以合法地声明变量?for循环的例子是规则的例外吗?这是否也适用于while和do while循环?
注意:即使我可以在那里声明char数组,我也不确定这是否在语法上正确,因为fgets()正在寻找指向字符的指针,而不是指向80个字符数组的指针。这就是为什么我尝试了malloc()版本的原因。

2
无论是否合法,您都不应该像那样调用malloc,因为您没有检查它是否返回NULL,也不能释放您正在分配的堆内存,因为fpath立即超出了作用域。 - Jeff Hammond
至少在Linux上,malloc很少返回null。并不是说你不应该检查,但要记住从malloc得到指针并不意味着实际上有足够的可用内存来使用。 - graywolf
在C语言中变量声明的位置应该放在函数或者代码块的最顶部,这样可以防止一些意外的错误。当然,在C99标准中,也允许在任何位置进行变量声明。但是为了保证代码质量和可读性,我们还是建议尽可能将变量声明放在最顶部。 - MarcH
3个回答

44

在C99中,你可以在需要的位置声明变量,就像C++允许你这样做一样。

void somefunc(char *arg)
{
    char *ptr = "xyz";
    if (strcmp(arg, ptr) == 0)
    {
        int abc = 0;    /* Always could declare variables at a block start */

        somefunc(arg, &ptr, &abc);

        int def = another_func(abc, arg);   /* New in C99 */
        ...other code using def, presumably...
    }
}
  • 您可以在“for”循环的控制部分声明变量:

  for (int x = 0; x < 10; x++)    /* New in C99 */
  • 在'while'循环或'if'语句的控制部分中不能声明变量。

  • 不能在函数调用中声明变量。

  • 显然,你可以(而且一直都可以)在任何循环或'if'语句后的块中声明变量。

  • C99标准规定:

    6.8.5.3 for语句

    语句为:

    for ( clause-1 ; expression-2 ; expression-3 ) statement
    

    以下是其行为方式:表达式expression-2是控制表达式,它在每次执行循环体之前进行评估。表达式expression-3在每次执行循环体后作为void表达式进行评估。如果clause-1是一个声明,则它声明的任何变量的作用域是剩余的声明和整个循环,包括另外两个表达式;它在执行顺序之前被执行以便在第一次评估控制表达式之前到达。如果clause-1是一个表达式,则它在第一次评估控制表达式之前作为void表达式进行评估。


    1
    感谢您提供详细的评论并引用标准。 - SiegeX
    §6.8.5.3 很容易找到,但我没有找到在哪个§中允许随处声明变量。有什么线索吗? - nowox
    1
    @nowox — C11 §6.8.3 复合语句:语法中将语句声明列为块项列表中出现的块项的等效选项。函数体当然是一个复合语句。 - Jonathan Leffler
    1
    @nowox — 作为对比,C90 §6.6.2“复合语句或块”的定义将复合语句列为{ declaration-list(opt) statement-list(opt) },因此在块中声明必须在所有语句之前。 - Jonathan Leffler

    9
    首先,我要注意的是您不应该混淆。
    for (int i = 0; i < 10; i++) {
        puts("hello");
    }
    

    并且

    fgets(char* fpath = malloc(80), 80, stdin);
    

    第一个是控制结构,而第二个是函数调用。控制结构以一种与函数调用非常不同的方式评估其括号()内的文本。
    第二个问题是...我不明白你所说的:
    编译器将立即给出错误提示,如果您尝试在for循环体内使用i。
    您列出的for循环代码是C语言中非常常见的结构,变量“i”确实应该在for循环体内可用。也就是说,以下代码应该可以工作:
    int n = 0;
    for (int i = 0; i < 10; i++) {
        n += i;
    }
    

    Am I misreading what you're saying?


    不,你没有看错,但显然我在我的问题中发布的SO链接上读到了一些不正确的信息,它说它不会编译。显然,更正确的陈述应该是如果不通过-std=c99传递给gcc,它将无法编译。您能否提供有关控制结构内文本评估与函数调用的更多见解,因为我认为这绝对是我问题的根源所在。 - SiegeX
    你所链接的问题中,发布了一个带有逻辑错误的for循环的示例(请注意for循环末尾的分号),导致编译失败。这是一个常见的错误,在SO上有许多相关的问题。 - nos

    3
    关于您对for/fgets的困惑,最重要的一点是,在C语言中,“括号控制作用域”是正确的规则,但在C99(从C++借鉴而来)中,还有另一条规则涉及作用域,即在控制结构(例如forwhileif)的开头声明的变量将在结构体的主体中保留作用域(并且不在主体外部保留作用域)。请注意保留HTML标记。

    2
    显然,这只适用于“for”循环,但对答案表示赞赏。 - SiegeX
    1
    哎呀。它在 C++ 中适用于 ifwhile,所以我猜想 C99 已经借用了整个东西,而不仅仅是 for 部分。 - Tyler McHenry

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接