【问题标题】:Don't we have to assign return values of the functions to variables? C/C++我们不必将函数的返回值分配给变量吗? C/C++
【发布时间】:2016-08-12 13:07:49
【问题描述】:

我已经使用 C/C++ 大约三年了,我不敢相信我以前从未遇到过这个问题!

以下代码可以编译(我刚刚尝试使用 gcc):

#include <iostream>

int change_i(int i) {
  int j = 8;
  return j;
}

int main() {
  int i = 10;
  change_i(10);
  std::cout << "i = " << i << std::endl;
}

而且,如您所料,程序会打印 i = 10。

我的问题是——为什么会编译?我本来预计会出现一个错误,或者至少是一个警告,说有一个未使用的值返回。

天真地,我认为这与您不小心忘记了非 void 函数中的返回调用类似。我明白它是不同的,我明白为什么这段代码本质上没有任何问题,但它似乎很危险。我刚刚在我的一些非常旧的代码中发现了一个类似的错误,代表一个可以追溯到很长时间的错误。我显然打算这样做:

i = change_i(10);

但是忘记了,所以它从来没有改变过(我知道这个例子很傻,确切的代码要复杂得多)。任何想法将不胜感激!

【问题讨论】:

  • 鉴于赋值 (=) 是一个表达式,因此对每个未使用的表达式发出警告都是有问题的。
  • 参见c++17中的nodiscard attribute
  • 您使用哪些选项进行编译?你在使用 -Wall 吗? gcc 还有其他标志可以打开/关闭警告。
  • 旁白:通常,人们会期望一个名为change_i 的函数来更改其参数;它必须将int&amp; 作为输入,而不是int

标签: c++ function return


【解决方案1】:

它编译是因为调用函数并忽略返回结果是非常常见的。事实上,main 的最后一行也是如此。

    std::cout << "i = " << i << std::endl;

实际上是:

    (std::cout).operator<<("i =").operator<<(i).operator<<(std::endl);

...并且您没有使用从最终operator&lt;&lt; 返回的值。

一些静态检查器具有在函数返回被忽略时发出警告的选项(然后是对返回经常被忽略的函数进行注释的选项)。 Gcc 可以选择将函数标记为需要使用返回值 (__attribute__((warn_unused_result))) - 但它仅在返回类型没有析构函数时才有效:-(。

【讨论】:

    【解决方案2】:

    忽略函数的返回值是完全有效的。以此为例:

    printf("hello\n");
    

    我们在这里忽略了printf 的返回值,它返回打印的字符数。在大多数情况下,您并不关心打印了多少个字符。如果编译器对此发出警告,每个人的代码都会显示大量警告。

    这实际上是忽略表达式值的特定情况,在这种情况下,表达式的值是函数的返回值。

    同样,如果你这样做:

    i++;
    

    您有一个表达式的值被丢弃(即 i 的值在被递增之前),但是 ++ 运算符仍然递增变量。

    赋值也是表达式:

    i = j = k;
    

    在这里,您有两个赋值表达式。一个是j = k,它的值是k的值(刚刚分配给j)。然后将该值用作i 的另一个赋值的右侧。然后丢弃i = (j = k) 表达式的值。

    这与不从非 void 函数返回值非常不同。在这种情况下,函数返回的值是未定义的,尝试使用该值会导致 undefined behavior

    忽略表达式的值没有任何不确定性。

    【讨论】:

    • 为了明确,也可以使用(void) discarded
    【解决方案3】:

    允许它的简短原因是因为这是标准规定的。

    声明

     change_i(10);
    

    丢弃change_i()返回的值。

    更长的原因是大多数表达式都有效果并产生结果。所以

    i = change_i(10);
    

    会将i 设置为8,但赋值表达式本身也有8 的结果。这就是为什么(如果jint 类型)

    j = i = change_i(10);
    

    将导致ji 都具有8 的值。这种逻辑可以无限期地继续下去——这就是为什么可以链接表达式,例如k = i = j = 10。所以 - 从语言的角度来看 - 要求将函数返回的值分配给变量是没有意义的。

    如果要显式丢弃函数调用的结果,可以这样做

    (void)change_i(10);
    

    和类似的声明

    j = (void)change_i(10);
    

    不会编译,通常是由于类型不匹配(int 不能被赋值为 void 类型的值)。

    话虽如此,实际上可以配置多个编译器(和静态代码分析器)以在调用者不使用函数返回的值时发出警告。此类警告默认关闭 - 因此需要使用适当的设置(例如命令行选项)进行编译。

    【讨论】:

      【解决方案4】:

      我已经使用 C/C++ 大约 三年

      我可以假设在这三年中,您使用了标准 C 函数 printf。例如

      #include <stdio.h>
      
      int main( void )
      {
          printf( "Hello World!\n" );
      }
      

      函数的返回类型不同于void。但是我确信在大多数情况下你没有使用函数的返回值。:)

      如果要求编译器在不使用函数的返回值时会发出错误,那么类似于上面所示的代码将无法编译,因为编译器无法访问函数的源代码并且无法判断函数是否有副作用。:)

      考虑另一个标准 C 函数 - 字符串函数。

      例如函数strcpy被声明为

      char * strcpy( char *destination, const char *source );
      

      如果你有例如以下字符数组

      char source[] = "Hello World!";
      char destination[sizeof( source )];
      

      那么函数通常被称为

      strcpy( destination, source );
      

      当您只需要复制一个字符串时,使用它的返回值是没有意义的。此外,对于显示的示例,您甚至可能不会写

      destination = strcpy( destination, source );
      

      编译器会报错。

      如您所见,有时忽略函数的返回值是有意义的。

      对于您自己的示例,编译器可能会发出一条消息,指出该函数没有副作用,因此它的调用已过时。在任何情况下,它都应该发出未使用函数参数的消息。:)

      考虑到有时编译器看不到某个其他编译单元或库中存在的函数定义。所以编译器无法判断一个函数是否有副作用,

      在大多数情况下,编译器处理函数声明。有时函数定义不适用于 C 和 C++ 的编译器。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-03-04
        • 2012-09-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多