我在古老的Linux(RedHat 5.2)和现代的macOS 10.14.6 Mojave上都使用GCC 9.2.0,并且两者都出现了同样的问题。
#include <stdio.h>
#include <time.h>
struct Example
{
/* ... */
char mm_yyyy[8]; /* Can't be changed */
/* ... */
};
extern void function(struct tm *tm, struct Example *ex);
void function(struct tm *tm, struct Example *ex)
{
snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
tm->tm_mon + 1, tm->tm_year + 1900);
}
当使用
-Wall
和任何优化(因此不是使用-O0
和没有任何优化选项)进行编译时,编译器会提示:$ gcc -O -Wall -c so-code.c
so-code.c: In function ‘function’:
so-code.c:15:49: warning: ‘%d’ directive output may be truncated writing between 1 and 11 bytes into a region of size 8 [-Wformat-truncation=]
15 | snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
| ^~
so-code.c:15:48: note: directive argument in the range [-2147483647, 2147483647]
15 | snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
| ^~~~~~~
so-code.c:15:48: note: directive argument in the range [-2147481748, 2147483647]
so-code.c:15:5: note: ‘snprintf’ output between 4 and 24 bytes into a destination of size 8
15 | snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 | tm->tm_mon + 1, tm->tm_year + 1900);
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$
在某种程度上,很好;如果
tm->tm_mon
包含超出范围0到99(或-1到-9)的值,则会将超过两个字节写入输出缓冲区,或者如果tm->tm_year + 1900
需要超过4位数字,则会发生截断/溢出。然而,时间值已知是有效的(月份0
到11
;年份+1900在范围1970
到2100
内,为了具体起见;实际年份范围较小——大约是2019..2025),因此实际上并不需要担心这个问题。有没有一种方法可以抑制警告,而不必诉诸于以下代码:
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignore "-Wformat-overflow" /* Or "-Wformat-truncation" */
#endif
snprintf(ex->mm_yyyy, sizeof(ex->mm_yyyy), "%d-%d",
tm->tm_mon + 1, tm->tm_year + 1900);
#ifdef __GNUC__
#pragma GCC diagnostic pop
#endif
#ifdef
行是必要的,因为代码必须由其他编译器(尤其是AIX上的XLC 13.x)编译,这些编译器会抱怨未知的pragma,尽管它们实际上不应该。更正式地说,它们不需要抱怨并且应该接受忽略未知的pragma的代码,但是它们会发出有关无法识别pragma的评论,这破坏了干净编译的目标。
仅仅为了好玩;如果将函数从返回void
改为返回int
,然后再return snprintf(…);
,则不会生成错误。(这让我感到惊讶-我不确定为什么这也不是一个问题。我想这与“从snprintf()
返回值被返回以便可以检查并发现溢出”有关,但这有点令人惊讶。)
不幸的是,这是一个MCVE(Minimal, Complete, Verifiable Example; 它所提取的代码要大得多,并且更改数据结构不是一个选项-而这只是出现在其中的函数中的许多步骤之一。
我想我可以编写一个微小的函数来调用snprintf()
并返回值(该值将被忽略),但是:
- 是否有其他可行的替代方法?
- 是否有一种方法告诉GCC传递给
snprintf()
等的变量范围比最坏情况更安全?
(tm->tm_mon + 1)%100u, (tm->tm_year + 1900)%10000u
吗?这是对原文的翻译,没有改变原意,也没有添加解释或其他额外内容。 - chux - Reinstate MonicaGCC 9.2.0
上吗? - l'L'l(void)
强制转换显式忽略snprintf的返回值是否可以停止警告? - Chris Doddstrftime()
没有提供一种获取1-9月份的月份数字的方法,据我所知。而且它提供的选项比标准Cstrftime()
更多。 - Jonathan Leffler