我在古老的 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
也不使用任何优化选项),编译器会说:
$ 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 或左右),因此担心实际上是没有根据的。1970
2100
有没有办法在不诉诸代码的情况下抑制警告,例如:
\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)编译,这些编译器会抱怨未知的编译指示,即使它们实际上并不应该这样做。(更正式地说,他们不需要抱怨,应该接受忽略未知编译指示的代码,但他们传递有关不识别编译指示的评论,这违背了干净编译的目标。)
只是为了好玩;如果将函数从“返回”更改void
为“返回” int
,并且随后return snprintf(\xe2\x80\xa6);
执行“返回”,则不会生成错误。(这让我感到惊讶 \xe2\x80\x94 我不确定我是否理解为什么这也不是一个问题。我想这与 \'的返回值有关snprintf()
以便可以检查它并发现溢出,因此,但这有点令人惊讶。)
不幸的是,这是一个 MCVE(最小、完整、可验证的示例));从中提取的代码要大得多,并且更改数据结构不是一个选项 \xe2\x80\x94,这只是该函数中许多步骤中的一个步骤它出现的地方。
\n\n我想我可以写一个微观函数来调用snprintf()
并返回值(该值将被忽略),但是:
snprintf()
比最坏情况更安全?使用检查可能值范围的编译器,使用%
来快速限制范围。
% 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
。