警告:忽略了带有“warn_unused_result”属性声明的'scanf'的返回值。

62
#include <stdio.h>

int main() {
    int t;
    scanf("%d", &t);
    printf("%d", t);
    return 0;
}

我使用ideone.com编译上述C代码时出现以下警告:
“prog.c: 在函数‘main’中:
prog.c:5: 警告:忽略带有属性warn_unused_result的‘scanf’的返回值”
请问有人可以帮助我理解这个警告吗?该警告表示忽略了scanf的返回值。
11个回答

77
你的libc编写者已经决定,scanf 的返回值在大多数情况下不应该被忽略,因此他们给它添加了一个属性,告诉编译器给出警告。
如果确实不需要返回值,则可以忽略。然而,通常最好检查它以确保你成功读取了你认为的内容。
在你的情况下,可以像这样编写代码以避免警告(和一些输入错误):
#include <stdio.h>

int main() {
    int t;
    if (scanf("%d", &t) == 1) {
        printf("%d", t);
    } else {
        printf("Failed to read integer.\n");
    }
    return 0;
}

1
我发现很奇怪的是:编译器似乎会发出有关未使用结果的警告,但只对scanf抱怨。也许很多人不知道,printf也返回一个值,但它似乎并不抱怨那里被丢弃的结果。 - flolo
8
编译器只会对带有特定属性的函数发出警告。如果 printf 失败,你几乎无能为力。但是 scanf 可能很容易失败。只需要输入一个字母 'A' 而不是整数,你的程序就会崩溃。 - Evan Teran
谢谢大家的回答。这个问题是今天才出现的。我之前已经使用了同样的编译器很多次,但突然出现了这个警告。 - vipin
@Evan Teran:啊,谢谢,我不知道编译器,以为它会是一个“全局”的标志/开关/属性。但既然它是一个函数属性,只有一些函数才有它们,那我就明白了。 - flolo
6
这是GCC的warn_unused_result函数属性的文档,如果你正在使用GCC(Ideone使用的就是),并且查看你的stdio.h头文件,你将会看到scanf及其相关函数被标记为warn_unused_result属性,但是printf及其相关函数则没有。 - Adam Rosenfield

20
警告(正确地)表明不检查scanf的返回值是一个坏主意。函数scanf已经通过gcc函数属性显式声明,如果您丢弃其返回值,则会触发此警告。
如果您真的想忘记这个返回值,同时让编译器(和您的良心)感到高兴,您可以将返回值转换为void:
(void)scanf("%d",&t);

36
即使你这样做,GCC仍会发出警告。有关更多信息,请参见此讨论 http://gcc.gnu.org/bugzilla/show_bug.cgi?id=25509。 - kraffenetti
6
如果你给一个虚拟变量赋值,那么你可能会得到另一个警告,即该变量被赋值但从未被使用。 - Craig McQueen
2
警告:许多编译器将优化掉这个无效的转换,因为它是不必要的,然后仍会产生未使用变量的错误。 - AffluentOwl
11
void 强制类型转换是通用标准,表示“故意未使用此结果”,被广泛认可,除了 GCC 之外。因为约20年前有人(Stallmann?)决定不应该有忽略 warn_unused_result 的方式。 - fuz
8
(void)! ... 可以用来避免 gcc 警告 (@kraffenetti @AffluentOwl),参见 https://gcc.gnu.org/bugzilla/show_bug.cgi?id=66425#c34。 - rivy
显示剩余2条评论

10

我尝试了您的示例代码,使用的是gcc(Ubuntu 4.4.3-4ubuntu5.1)4.4.3版本。 只有在进行优化编译时(例如使用 -O2 或 -O3 选项),才会发出警告。 请求所有警告(-Wall选项)不起作用。 将任意类型转换为void指针的经典习惯用法没有作用,它不能抑制该警告。

我可以通过编写以下内容来消除警告:

if(scanf("%d",&t)){};

这段代码虽然可行,但对我来说有点晦涩。使用Empty {}可以避免另一个警告-Wempty-body。


2
这可能会导致编译器发出额外的空主体警告。 - kraffenetti
2
如果我看到那种在 if 结尾处带有分号的代码,我会认为它可能是一个 bug。至少需要一个解释性注释。 - Craig McQueen
3
这样不负责任、像牛仔一样的建议居然获得这么多赞??令人毛骨悚然。 - Mawg says reinstate Monica
Mawg是正确的,你应该始终检查scanf的返回值。我正在寻找一种消除它的方法,更多的是作为一种娱乐,而不是良好编码的建议。我仍然觉得奇怪的是(a)优化会影响是否收到警告,以及(b)-Wall不会。 - Quigi

5

请执行以下操作:

int main() {
    int t;
    int unused __attribute__((unused));
    unused = scanf("%d",&t);
    printf("%d",t);
    return 0;
}

2
很遗憾,这看起来不太可移植。 - Tino
这是一个规避可能错误的方法;您应该检查scanf的返回值是否符合您的预期(在这种情况下应该为1)。 - Woodrow Douglass

4
阅读了本页面上的所有回答和评论后,我没有看到这些避免警告的另一种选项: 在使用gcc进行编译时,您可以在命令行中添加以下内容: gcc -Wall -Wextra -Wno-unused-result proc.c -o prog.x 另一个选择是使用-O0作为“优化级别零”,它会忽略警告。 在使用gcc编译时,使用(void)类型转换是毫无用处的。 如果调试代码,您可以始终像下面的示例一样使用assert():
u = scanf("%d", &t);
assert(u == 1);

但现在,如果您通过 #define NDEBUG 关闭断言,则会得到一个 -Wunused-but-set-variable。然后,您可以通过以下两种方式之一关闭此第二个警告
  1. 在您的 gcc 命令行中添加 -Wno-unused-but-set-variable,或者
  2. 使用属性声明变量:int u __attribute__((unused));
正如其他答案所指出的那样,尽管第二种选项似乎是最佳选择,但它不太可移植。
最后,下面定义的可以帮助您忽略给定函数的返回值,但如果您不想关闭所有未使用的函数返回值的警告,则可能感到不舒适:
#define igr(x) {__typeof__(x) __attribute__((unused)) d=(x);} 

double __attribute__ ((warn_unused_result)) fa(void) {return 2.2;}
igr(fa());

另请参阅此答案


1
“assert”是一种滥用。如果“assert”失败,程序员编写的代码就有缺陷。如果我在您的程序中输入“A”,您的代码将中止,因此它存在故障。 - Antti Haapala -- Слава Україні
1
谢谢您的评论。我同意,如果您像从我的回答中误解的那样使用assert,确实是一种滥用。上面的答案列出了各种处理“未使用scanf返回值”的选项,这是主要问题。assert部分说明如果调试。我认为将所有选项“写下来”作为教育性信息回答是很好的,这样人们就可以学习所有工具并做出明智的选择。这也是我认为您的评论有帮助的原因,这样人们就不会错误地滥用assert()来完成不适合它的工作。谢谢。 - DrBeco

3
一种解决方法是使用下面所示的 IGUR() 函数。非常丑陋,但仍然有些可移植性。(对于不理解 inline 的旧编译器,只需像往常一样定义 #define inline /*nothing*/ 即可。)
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

inline void IGUR() {}  /* Ignore GCC Unused Result */
void IGUR();  /* see https://dev59.com/W2Qo5IYBdhLWcg3wMs-2#16245669 */

int
main(int argc, char **argv)
{
  char  buf[10*BUFSIZ];
  int   got, fl, have;

  fl    = fcntl(0, F_GETFL);
  fcntl(0, F_SETFL, fl|O_NONBLOCK);
  have = 0;
  while ((got=read(0, buf, sizeof buf))>0)
    {
      IGUR(write(1, buf, got));
      have = 1;
    }
  fcntl(0, F_SETFL, fl);
  return have;
}

顺便说一下,这个例子以非阻塞方式从 stdin 复制到 stdout,直到所有等待的输入都被读取完毕,如果没有任何内容,则返回 true(0),否则返回false(1)。 (它可以防止在像 bash 中的 while read -t1 away; do :; done 中出现1秒的延迟。)

-Wall(Debian Jessie)下编译时不会出现警告。

编辑:IGUR() 也需要在没有 inline 的情况下定义,以便它可以供链接器使用。否则,在使用 cc -O0 时可能会失败。请参见:https://dev59.com/W2Qo5IYBdhLWcg3wMs-2#16245669

编辑2:新版本的 gcc 要求在 void 前放置 inline


我刚刚发现在bash中可以写成read -t0.01 away,这样就只会添加100分之一秒的延迟。不应该低于这个值,因为如果进程被减速(例如在strace下运行),计时器可能会在read()执行之前触发,从而导致read无效。 - Tino

2

实际上这取决于您的需求,如果您只想禁用编译器警告,可以通过强制转换忽略函数的返回值,或者您也可以处理它,scanf函数的含义是用户输入的数量。

==== 更新 ====

您可以使用

(void) scanf("%d",&t);

来忽略scanf的返回值。


1
批评忽略警告是不明智的。比你我聪明的人对此负有责任。警告信息会提供有用的提示,关注它们可以提高代码质量。如果忽略这些警告,可能会带来危险,请不要鼓吹他人忽略或抑制警告信息。 - Mawg says reinstate Monica
4
无论如何,我知道警告出现在哪里,而且我知道它会对我的程序造成伤害。有些项目可能会将警告视为错误。我的意思是要消除警告。 - Acton
4
对于 gcc 5.4.x,这种方法已经不再有效。当在函数前加上 (void) 强制类型转换时,我仍然会收到同样的警告。 - Carlo Wood
1
警告并非错误。当您看到警告时,应检查该行并确定其是否已经没有问题。在某些情况下,确实要忽略scanf的返回值,因此没有办法为该行删除警告,这很愚蠢,直接说吧。您不希望已经检查并确定安全的代码发出警告,因为这会增加编译器输出中的噪声/信号比。 - Lelanthran
@WoodrowDouglass 实际上这取决于你的需求,如果你只想禁用编译器的警告,你可以通过强制转换来忽略函数的返回值,或者你也可以处理它,这是基本技能。 - Acton
显示剩余3条评论

2

有人能帮我理解这个警告吗?

不行,但是我可以为警告抑制的恐怖做出贡献。为了积极地将返回值丢弃,优雅的方法是将我们的语句包装在一个易于理解的lambda函数中,像这样:

[&]{ return scanf("%d", &t); }();

抱歉。

这里有一种优雅之处,就像 JavaScript 中的 IIFE 习语。但是就像 IIFE 一样,请小心不要省略尾随的()!我认为在这种情况下可能会收到警告,但我使用 -Wall 没有收到任何警告。 - Paul Brannan

1

scanf和printf是返回值的函数,通常在这些函数中返回的是读取或写入的字符数。如果发生错误,您也可以通过返回代码捕获错误。 良好的编程实践是查看返回值,但是我从未见过有人查看printf的返回值...

如果您想要消除警告,您可能可以更改编译器的严重性。


0

只需使用一个 if() 包围和一个空块,终止的分号必须在下一行(以防止额外的警告)

#include <stdio.h>
main (int argc, char const *argv[])  {
...
if ( scanf("%d",&n) )
    ;
...
return 0;
}

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