忽略 C 语言中的返回值

51

最近,我开始使用lint进行静态代码分析。有时候,我会遇到以下问题之一的警告。比如说,如果我有下面这个函数:

uint32_t foo( void );

假设我故意忽略函数的返回值。 为了让警告消失,可以这样写:

(void) foo();

我的问题是,像这样编写代码的“适当”方式是什么,我应该继续像往常一样,因为编译器没有抱怨,还是应该使用void来表明清晰,以便其他代码维护者知道我故意忽略了返回值。

当我看到带有void的代码时,它对我来说看起来相当奇怪...


2
你为什么故意忽略返回值? - bpeikes
22
有些情况下返回值并不重要,例如在嵌入式系统中,如果用于记录日志的printf函数失败了,你其实也无能为力...你只能尝试向用户写入无法使用printf的消息? :) - stdcall
1
通常情况下,您可以禁止特定函数的错误消息报告。这取决于您使用的Lint,但在PC Lint中,我有类似于“-ecall(534,printf)”的东西,它会停止报告对printf的调用未使用返回值(534)。大多数其他Lint都有类似的机制。 - Andy Krouwel
在产品上设置一个专用的LED指示灯,用于显示“printf失败”。 - Daniel Chin
10个回答

78

通常的方法是直接调用foo();而不需要强制转换为(void)

从来没有忽略过printf()的返回值的人,请投掷第一块石头。


8
在四个回答中,只有一个回答者理解了问题,需要将该回答加一分。不确定为什么其他人都没理解问题。 - Egor
1
值得注意的是:Windows 中有一个 UNREFERENCED_PARAMETER 用于未使用的参数;您可以为其他表达式创建类似的宏。 - user541686
9
我个人认为,在意识到printf返回一个值之前,我已经花了最长的时间,哈哈。 - Dennis Meng
3
好的,我会尽力进行翻译。 “只是这样做是不行的。看看@dfsmith的回答。” - m4l490n
任何代码维护者如果不能从缺少赋值运算符推断出返回值被忽略,那么可能不应该维护代码。如果程序正在“开发”中,这可能是一个疏忽。如果它正在维护中,则合理地假定这是一个有意的选择。 - Chindraba

39
我个人喜欢“未使用”的警告,但偶尔会有一些情况需要忽略它们(例如向用户write() ,或 fscanf(...,"%*s\n")strtol() 返回值不重要,只想要 [也许] 移动文件指针的副作用。)
使用gcc 4.6,这变得非常棘手。
  • 强制转换为(void) 不再起作用。
  • 重新编写函数(特别是变参函数)很繁琐且笨拙。
  • {ssize_t ignore; ignore=write(...);} 抛出另一个警告(赋值但未使用)。
  • write(...)+1 抛出另一个警告(计算结果未使用)。
唯一好的(虽然丑陋)方法是将返回值转换为编译器同意忽略的内容。
例如:(void)(write(...) + 1)
这显然是进步。(顺便说一下,+0 不起作用。)

非常感谢您的技巧!虽然您没有回答原始问题(如何避免“lint”警告),但您回答了另一个非常合理的问题(如何避免使用gcc时出现该警告)。 - Kai Petzke
忽略write()的返回值实际上并不是一个好主意。如果我没有忽略它,我本可以更快地发现一些错误。 - kec
3
if(write(...)); 也应该可以工作。不要忘记加上尾部的分号; - con-f-use
类似于con-f-use,我刚刚采用了类似的倾向:但我加入了一个调试输出。在Qt中很容易实现,也是明智的做法。如果我在桌面上运行代码,我会得到有关出错情况的合理警告,但在发布代码时不会发生任何事情。 - LovesTha
1
这应该是正确的答案。只是将其转换为(void)并不起作用。 - m4l490n
2
(void)!foo(); /*works with gcc4.6+ */ 最好放在宏或内联函数中。最好使用支持“不能忽略”的工具。有些返回是信息性的,而其他则是关键的。 - artless noise

24

使用Clang和GCC编译器之一实现此操作的一种方法是使用pragma

    /* ... */

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-result" 

    foo(); /* this specific unused-result warning gets ignored during compilation */

#pragma GCC diagnostic pop 

    /* ... */

push-pop组合包装了ignored指令,以便可以在代码的其他地方触发警告。这样做可以使日后阅读源代码的人更容易看出此代码块的作用。


13

一个稍微更加 "美观" 的方式来指示未使用的结果是这样的:

/**
 * Wrapping your function call with ignore_result makes it more clear to
 * readers, compilers and linters that you are, in fact, ignoring the
 * function's return value on purpose.
 */
static inline void ignore_result(long long int unused_result) {
    (void) unused_result;
}

...

ignore_result(foo());

使用 C++,可以扩展为:

template<typename T>
inline void ignore_result(const T & /* unused result */) {}

3
这是我首选的解决方案。但是,我必须说,一个能够正常工作的C示例是: static inline void ignore_result(ssize_t unused) { (void)unused; } ,因为在C中您不能像在C++中那样省略参数标识符。此外,如果您在gcc中没有添加 static ,且禁用优化,则会生成链接器错误。 - Watcom
2
谢谢您的注意,我已经相应地修正了我的答案。 - mic_e
在提供的各种答案中,这也是我在2019年首选的解决方案。谢谢! - Xunnamius
更新了C++解决方案,将按值传递的参数替换为const引用参数,即使在奇怪的边缘情况下也保证不会产生副作用。 - mic_e

9
为了使静态代码检查器有用,它应该报告被忽略的返回值,这些返回值往往会导致难以跟踪的错误或缺失的错误处理。
因此,您应该保留 (void) 或停用 printf 的检查。现在,您有几个选项可以以可读的方式执行此操作。例如,我通常会将函数包装在一个新函数中。
void printf_unchecked(const char* format, ...)

这里是不太好的类型转换发生地。也许在这种情况下,使用预处理器宏更为实际,因为有可变参数...


2
尽管我个人更喜欢使用简单的(void)转换,但请返回+1。感谢您理解问题并提供不同的方法。 - stefan
2
我喜欢使用预处理器宏的想法。也许可以这样:#define UNCHECKED_RV(function_call) (void)function_call这种解决方案或者使用 function_unchecked() 包装器的好处在于,它非常清楚地表明你有意忽略了返回值。将其转换为 void 并不那么清晰。 - bpeikes

8

我喜欢使用以下标志编译我的代码:

$gcc prog1.c -o prog1.x -Wall -Wextra -ansi -pedantic-errors -g -O0 -DDEBUG=1

为了避免 -Wunused-result,我不喜欢添加另一个标志:-Wno-unused-result(如果你这样做,那是一种解决方案)。
我曾经对某些函数进行过(void)类型转换(不包括printf或其他常用函数,因为编译器不会对它们发出警告,只会对奇怪的函数发出警告)。现在,(void)类型转换不再有效(GCC 4.7.2)。
有趣的Splint建议:
Result returned by function call is not used. If this is intended,
can cast result to (void) to eliminate message. (Use -retvalother to
inhibit warning)

但这已经不再是一个解决方案了。Splint需要更新以解决此问题。

因此,为了以一种非常兼容的方式消除警告,这里有一个不错的

/** Turn off -Wunused-result for a specific function call */
#define igr(M) if(1==((long)M)){;}

然后像这样调用:

igr(PL_get_chars(t, &s, CVT_VARIABLE));

这是一种简洁的写法,任何编译器都可以消除这段代码。下面是我喜欢的编辑器vi的图片:左边窗口没有igr();中间窗口使用了igr();右边窗口是源代码。

enter image description here

你可以看到,它们完全相同,这是一个完全无害的代码,让C做gcc不允许的事情:忽略返回值。

比较1==...只是为了避免splint警告,这个条件不是BOOL。GCC并不在意。根据函数的不同,你可能会得到一个cast警告。我用这个宏忽略了一个double进行了测试,结果很好,但我仍然不太确信。特别是如果函数返回一个指针或更复杂的东西。

在这种情况下,你还需要:

#define pigr(M) if(NULL==((void *)M)){;}

最后一件事:由于-Wempty-body警告(建议在“if”语句周围加上大括号),所以{;}是必要的。
而且(现在最后一件事),函数调用后面的;不是(严格)必要的,但这是良好的编程习惯。使您的代码行更加统一,所有行都以;结尾。(它被翻译为NOP助记符,并在优化后消失)。
编译器没有警告或错误。运行splint会产生:
$ splint ./teste.c -I/usr/lib/swi-prolog/include/ -strict-lib
Splint 3.1.2 --- 20 Feb 2009

Finished checking --- no warnings

请参见此答案


1
顺便说一句,如果您知道自己在做什么,并且可能的话,请检查返回值,这样会更好。关于我提供的这个例子,在使用SWI-Prolog时,我的解决方案是编辑SWI-Prolog.h文件,并允许使用GCC键“-DWUNUSED =”重新定义WUNUSED __attribute__ ((warn_unused_result)) - DrBeco
最好提到-Wno-unused-result - qwr

3

gnulib提供了以下内容:

http://git.savannah.gnu.org/cgit/gnulib.git/tree/lib/ignore-value.h
/* Normally casting an expression to void discards its value, but GCC
   versions 3.4 and newer have __attribute__ ((__warn_unused_result__))
   which may cause unwanted diagnostics in that case.  Use __typeof__
   and __extension__ to work around the problem, if the workaround is
   known to be needed.  */
#if 3 < __GNUC__ + (4 <= __GNUC_MINOR__)
# define ignore_value(x) \
    (__extension__ ({ __typeof__ (x) __x = (x); (void) __x; }))
#else
# define ignore_value(x) ((void) (x))
#endif

谢谢,这很有帮助!gcc 4+和__warn_unused_result__是我的情况。 - sqr163

1
另一种解决方案是实际使用一个值。然后,您可以使用宏来删除“未使用的变量”警告。
#define _unused(x) ((void)(x))

然后在你的例子中,你会有:

然后在你的例子中,你会有:

val = foo();
_unused(val); 

1
通常情况下,你不会想要忽略太多的函数值。例如,Splint 允许添加一个特殊的注释,让它知道某个特定函数的返回值可能被忽略。不幸的是,这实际上会禁用所有与该特定函数相关的“忽略返回值”警告。
以下是一个 Splint-clean 程序的示例:
#include <stdio.h>

FILE /*@alt void@*/ *fopen(const char *path, const char *mode);

static int /*@alt void@*/ test(void)
{
   printf( "test called\n" );

   fopen( "test", "a" );

   return 0;
}

int main(void)
{  
   test();

   return 0;
}

不愉快的部分在于您需要在某个地方添加一个附加的原型函数和注释。

顺便提一下,默认情况下,Splint 不会对 printf 和一些其他 libc 函数的返回值未使用发出警告。但是,可以激活更严格的模式。

LINT 允许类似的操作,但我从未使用过。以下是文档中的说明:

LINT 允许您使用与 C 预处理器的 # 指令类似的指令来标记具有可选返回值的函数。

#pragma optresult

可以立即放置在返回可选结果的函数的定义之前。然后 LINT 就会认识到该函数返回可以被忽略的结果;如果结果被忽略,LINT 不会给出错误信息。


-1

在某些情况下编写忽略返回值的代码是完全合法且可接受的。下面的程序很少有理由检查printf()的返回值。

int main(void) {
  printf("Hello world\n");
  return 0;
}

5
我知道这是合法的,但那不是我的问题,我的问题是我是否应该向其他程序员指明这一点... - stdcall
1
@Mellowcandle说你不用这样做,而我则同意他的观点。 - Carl Norum
不符合MISRA-C2012标准。规则17.7。 - Radzor

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