如何阻止 GCC 在 snprintf() 调用中抱怨“指令输出可能被截断”?

Jon*_*ler 8 c gcc

我在古老的 Linux (RedHat 5.2) 和现代 macOS 10.14.6 Mojave 上使用 GCC 9.2.0,并且我在两者上都得到了同样的抱怨。

\n\n
#include <stdio.h>\n#include <time.h>\n\nstruct Example\n{\n    /* ... */\n    char mm_yyyy[8];    /* Can\'t be changed */\n    /* ... */\n};\n\nextern void function(struct tm *tm, struct Example *ex);\n\nvoid function(struct tm *tm, struct Example *ex)\n{\n    snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",\n             tm->tm_mon + 1, tm->tm_year + 1900);\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

当使用 和 进行任何优化编译时-Wall(所以不使用-O0也不使用任何优化选项),编译器会说:

\n\n
$ gcc -O -Wall -c so-code.c \nso-code.c: In function \xe2\x80\x98function\xe2\x80\x99:\nso-code.c:15:49: warning: \xe2\x80\x98%d\xe2\x80\x99 directive output may be truncated writing between 1 and 11 bytes into a region of size 8 [-Wformat-truncation=]\n   15 |     snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",\n      |                                                 ^~\nso-code.c:15:48: note: directive argument in the range [-2147483647, 2147483647]\n   15 |     snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",\n      |                                                ^~~~~~~\nso-code.c:15:48: note: directive argument in the range [-2147481748, 2147483647]\nso-code.c:15:5: note: \xe2\x80\x98snprintf\xe2\x80\x99 output between 4 and 24 bytes into a destination of size 8\n   15 |     snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",\n      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n   16 |              tm->tm_mon + 1, tm->tm_year + 1900);\n      |              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n$\n
Run Code Online (Sandbox Code Playgroud)\n\n

一方面,这很公平;另一方面,如果tm->tm_mon包含 0 到 99(或 -1 到 -9)范围之外的值,则超过两个字节将被写入输出缓冲区,或者如果tm->tm_year + 1900需要超过 4 位数字,则会出现截断/溢出。但是,已知时间值是有效的(为具体起见,月份0至;年份 + 1900 在至11范围内;年份范围实际上较小 \xe2\x80\x94 2019 .. 2025 或左右),因此担心实际上是没有根据的。19702100

\n\n

有没有办法在不诉诸代码的情况下抑制警告,例如:

\n\n
#ifdef __GNUC__\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignore "-Wformat-overflow" /* Or "-Wformat-truncation" */\n#endif\n\n    snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",\n             tm->tm_mon + 1, tm->tm_year + 1900);\n\n#ifdef __GNUC__\n#pragma GCC diagnostic pop\n#endif\n
Run Code Online (Sandbox Code Playgroud)\n\n

这些#ifdef行是必要的,因为代码必须由其他编译器(特别是 AIX 上的 XLC 13.x)编译,这些编译器会抱怨未知的编译指示,即使它们实际上并不应该这样做。(更正式地说,他们不需要抱怨,应该接受忽略未知编译指示的代码,但他们传递有关不识别编译指示的评论,这违背了干净编译的目标。)

\n\n

只是为了好玩;如果将函数从“返回”更改void为“返回” int,并且随后return snprintf(\xe2\x80\xa6);执行“返回”,则不会生成错误。(这让我感到惊讶 \xe2\x80\x94 我不确定我是否理解为什么这也不是一个问题。我想这与 \'的返回值有关snprintf()以便可以检查它并发现溢出,因此,但这有点令人惊讶。)

\n\n

不幸的是,这是一个 MCVE(最小、完整、可验证的示例));从中提取的代码要大得多,并且更改数据结构不是一个选项 \xe2\x80\x94,这只是该函数中许多步骤中的一个步骤它出现的地方。

\n\n

我想我可以写一个微观函数来调用snprintf()并返回值(该值将被忽略),但是:

\n\n
    \n
  • 还有其他可行的替代方案吗?
  • \n
  • 有没有办法告诉GCC传递给的变量的范围snprintf()比最坏情况更安全?
  • \n
\n

chu*_*ica 8

使用检查可能值范围的编译器,使用%来快速限制范围。

% some_unsigned_N确保输出在 [0... N-1].

请注意,% some_pos_int_N输出在(-N ...N)范围内,因此建议 使用无符号数学以避免'-'符号。

snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d", 
    //  tm->tm_mon + 1, tm->tm_year + 1900);
    (tm->tm_mon + 1)%100u, (tm->tm_year + 1900)%10000u);
Run Code Online (Sandbox Code Playgroud)

可能还想用"%u"应该some_unsigned_N近的INT_MAX

  • 谢谢。这确实阻止了投诉,并且还确保这些值在预期范围内,并且没有理由认为其他编译器会拒绝它,因此它符合我的标准。(另一个编译器集挑剔可能不会接受这使它安全 - 如果我到达它,我会跨过那座桥。我可能需要处理有关混合有符号和无符号算术的投诉,但这也可以处理如有必要,稍后再进行。) (2认同)