在C语言中函数参数中声明变量。

5

我有一个有点奇怪的愿望,不知道是否有任何编译器或语言扩展可以实现。

我想能够在函数调用内部声明变量,就像这样:

int test(int *out_p) {
    *out_p = 5;
    return 1;
}

int main()
{
    if (int ret = test(int &var)) { // int var declared inside function invocation
        fprintf(stderr, "var = %d\n", var); // var in scope here
    }
    return 0;
}

因此,变量的作用域遵循 ret 的作用域。举个例子(来自我现在正在处理的项目),我有:

cmd_s = readline();
int x, y, dX, dY, symA, symB;
if (sscanf(cmd_s, "placeDomino:%d %d atX:%d y:%d dX:%d dY:%d",
                           &symA, &symB, &x,  &y,   &dX,  &dY) == 6) {
    do_complicated_stuff(symA, symB, x, y, dX, dY);
} else if (sscanf(cmd_s, "placeAtX:%d y:%d dX:%d dY:%d", &x, &y, &dX, &dY) == 4) {
    do_stuff(x, y, dX, dY);
    /* symA, symB are in scope but uninitialized :-( so I can accidentally
     * use their values and the compiler will let me */
}

and I would prefer to write

cmd_s = readline();
if (sscanf(cmd_s, "placeDomino:%d %d atX:%d y:%d dX:%d dY:%d",
                    int &symA, int &symB, int &x, int &y, int &dX, int &dY) == 6) {
    do_complicated_stuff(symA, symB, x, y, dX, dY);
} else if (sscanf(cmd_s, "placeAtX:%d y:%d dX:%d dY:%d", int &x, int &y, int &dX, int &dY) == 4) {
    do_stuff(x, y, dX, dY);
    /* Now symA, symB are out of scope here and I can't
     * accidentally use their uninitialized values */
}

我的问题是,是否有任何编译器支持这个特性?如果我正确使用它,gcc是否支持它?是否有C或C++(草案)规范包含此功能?
编辑:刚刚意识到在我的第一个代码示例中,int ret的声明也不符合C99标准;我想我被for循环宠坏了。我也想要那个特性;想象一下。
while(int condition = check_condition()) {
    switch(condition) {
        ...
    }
}

或者类似的东西。


1
我确实说过“离奇的愿望”:-p 我通常发现简洁可以提高清晰度;这就是为什么我更喜欢使用那些可以让我简洁表达的习语。不过,对于这个特定的习语,我可能运气不佳。 - zmccord
@vulkanino 当然。你知道现在键盘要多少钱吗?我们至少每十年需要花费10美元来购买键盘!显然,键盘的磨损应该是任何成功程序员的主要关注点。 - Lundin
@zmccord 你可能需要澄清一下你的兴趣是在生产代码中使用它,还是只是对C语言的内部工作原理感到好奇。如果是后者,请在帖子中添加标签“language-lawyer”,以防止其被关闭。 - Lundin
我确实希望在生产代码中使用此功能;虽然我在原始帖子中没有表达清楚,但我知道这在C99中不是标准,而是更多地针对具有此功能的扩展方言。我正在工作的项目中的示例是认真的;我希望这个功能可以缩短我的代码并帮助编译器在这个现实项目中捕捉我的错误。 - zmccord
许多人似乎没有看到这个的用处,但我认为这样的语法对于确保输出变量在使用之前可用是有用的。许多函数返回成功/失败并设置输出参数。如果函数失败,输出参数的值可能不会改变,或者更糟糕的是未定义的。有能力使这些输出参数仅在函数成功时可用将是有用的。 - Rufus
显示剩余3条评论
4个回答

5
除了块级作用域声明外,C99基本上有两种其他方式来声明变量,这些变量的定义仅限于它们出现的语句中:
  • 复合字面量的格式为(type name){ initializers },声明一个生存在当前块中的局部变量。例如,对于函数调用,您可以使用test(&(int){ 0 })
  • for语句中的变量只在for语句本身以及相应的语句或块中具有作用域。

您可以在带有局部变量的if表达式中做出一些奇怪的事情,比如:

for (bool cntrl = true; cntrl; cntrl = false)
   for (int ret = something; cntrl && test(&ret); cntrl = false) {
      // use ret inside here
   }

要小心,这样的代码很快就会变得难以阅读。另一方面,优化器可以将此类代码有效地减少到基本内容,并轻松发现testfor块的内部只被评估一次。


除了有趣的 IOCCC 材料,我们到底从中获得了什么? - Lundin
@Lundin,你的暗示是关于隐藏的for作用域变量吗?也许,仅从C ++中导入块语句局部变量的概念并不是一个好主意? - Jens Gustedt
复合字面量形式对于需要传递包装值的函数非常有用,其中您需要将C类型包装在盒子中并将其传递给函数。即 #define BOXED_INT32(_val) &(value_box_t){ .type = INT32, .datum.int32 = _val}my_boxy_func(BOXED_INT32(42)) - Arran Cudbard-Bell
只要盒子类型和 C 类型之间存在一对一的映射关系,就可以将其与 C11 _Generic 结合起来(创建一个 BOX 宏,根据 C 类型自动选择盒子类型)。 - Arran Cudbard-Bell

1
while(int condition = check_condition()) {
    switch(condition) {
        ...
    }
}

或者

if (int ret = test(int &var))

我的问题是,是否有任何编译器支持这个?如果我正确使用gcc,它是否支持?是否有C或C++(草案)规范包含此内容?
这不是C语言。if语句或while语句的子句必须是表达式,不能是声明。
自C99以来,您只能在for迭代语句的第一个子句中拥有一个声明。
for (clause-1; expression-2; expression-3)

clause-1 可以是声明或表达式。


0

使用空作用域,像这样:

int test(int *out_p) {
    *out_p = 5;
    return 1;
}

int main()
{
    {
        int var, ret;
        if (ret = test(&var)) {
            fprintf(stderr, "var = %d\n", var); // var in scope here
        }
    }
    // var not in scope
    return 0;
}

是的,那样做可以;但它缺乏简洁性。我猜我所建议的并没有更加简明扼要,但我希望至少有一点语法糖。 - zmccord
3
这不是C语言,if条件语句必须是一个表达式,而不能是一个声明。 - ouah
我将声明移动到与变量相同的级别。 - Johan

0

没有编译器支持这个。我不明白这有什么意义。

减少源代码行数并不一定会导致更高效的程序,这是一个常见的误解。在 C 语言中,在 99% 的情况下,将像这样的语句重写为更紧凑的语句是没有意义的。这只会导致代码难以阅读,最终得到的机器码也是相同的。

你应该这样做:

void some_func (void) // good example
{
  ... lots of code here

  int ret;
  int var;

  ret = test(&var);
  if(ret == SOMETHING)
  {
    fprintf(stderr, "var = %d\n", var); // var in scope here
  }
}

你不应该做的是这样:

void some_func (void) // bad example
{
  ... lots of code here

  {
    int ret;
    int var;

    if((ret = test(&var))
    {
      fprintf(stderr, "var = %d\n", var); // var in scope here
    }
  }
}

好的例子和坏的例子将产生完全相同的机器代码。这一点非常重要!

首先,坏的例子中变量的范围缩小并不会导致更有效的程序:编译器非常擅长知道何时第一次使用变量以及何时不再使用。在两个示例中,编译器将在CPU寄存器或堆栈中存储变量ret和var。

还要注意的是,无论变量是在函数中间声明(仅限C99/C11)还是在开头声明(C90或C99/C11),对于效率来说都没有任何影响。在作用域中间声明变量只是一种编程风格特征:您告诉代码的读者从这一点开始这个变量开始起作用。与阅读代码的人不同,编译器不关心您在哪里编写了声明。

如果我们省略了ret并只检查test()的结果,那也不会有任何区别-函数的结果仍然必须在某个地方保存,无论是在程序员明确声明的变量中还是在编译器隐式创建的临时变量中。机器代码将是相同的,如果没有ret变量,则调试将更加困难。

这些示例之间的主要区别在于糟糕的示例包含广泛认可的不良编程实践,例如条件内的赋值(MISRA-C:2004 13.1)和非布尔变量的隐式零测试(MISRA-C:2004 13.2)。再加上不必要、晦涩的额外本地范围。

因此,我的建议是学习一些汇编语言(或反汇编),了解C代码实际上是如何转换为机器代码的。当你知道这一点时,你就不会感到有必要用错误的假设来不必要地混淆代码,以为自己正在改进它。


我实际上并不关心输出汇编,事实上,“不必要”的局部作用域正是我想要的唯一东西。没有必要居高临下;我知道现代编译器有活性分析和寄存器着色等好处。对于触发你对愚蠢“优化”的厌恶,我也有同感,但我希望你把怒火留给那些真正试图愚蠢地进行优化的人们。 - zmccord
我的观点是,如果你的论点不是优化,那么唯一剩下的论点就是可读性和维护性,而像这样的更改显然会 降低 可读性和维护性。 - Lundin
我想我可能会降低对C程序员的可读性。我更习惯于使用功能更丰富的语言;在其他语言中,通过返回值可以轻松获得在C中通过传递指向值的指针获得的功能。例如,在perl中,我可以说“my ($foo, $bar) = baz();”,同时绑定foo和bar,并与函数调用同时进行;我可以在SML和Haskell中做类似的事情。我希望能够模仿这种熟悉的模式,以增加可读性、可调试性和可维护性,但我想对于那些首选语言为C的人来说,这可能更加难以理解。 - zmccord
由于我写的语言是C,这证明了你的观点;代码很可能会被C程序员阅读。令人高兴的是,虽然我没有提到这一点,但我正在开发一个唯一的程序员就是我自己的项目,所以其他人对我的风格特点的看法不那么重要。总的来说,你是正确的;大多数C程序员可能不太关心这个特性,如果我要与其他人共享代码,我也不会使用它。 - zmccord
1
他们在C# 7.0中添加了一个语义相似的功能,称为“out变量”,但限制是在C#中仅限于标记为out的函数参数。 - ceztko

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