我在古老的Linux(RedHat 5.2)和现代的macOS 10.14.6莫哈韦上使用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
; year + 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)编译,这些编译器会抱怨未知的杂注,即使它们实际上并不应该这样做。(更正式地说,它们不需要抱怨,并且应该接受忽略未知杂注的代码,但它们会传递关于不识别杂注的注解,这违背了干净编译的目标。
只是为了好玩;如果你将函数从返回void
改为返回int
,如果你再返回return snprintf(…);
,则不会生成错误。(这让我很惊讶,我不知道为什么这不是一个问题。我猜这是因为snprintf()
的返回值被返回,所以可以检查它并发现溢出,但这有点令人惊讶。)
不幸的是,这是一个MCVE(Minimal, Complete, Verifiable Example;它所提取的代码要大得多,并且改变数据结构不是一个选项-这只是它出现的函数中许多步骤中的一个步骤。
我想我可以写一个微观函数来调用snprintf()
并返回值(将被忽略),但是:
- 有没有其他可行的替代方案?
- 有没有办法告诉GCC传递给
snprintf()
等的变量范围比最坏情况更安全?
2条答案
按热度按时间pobjuy321#
对于检查可能值范围的编译器,可以使用
%
快速限制范围。% some_unsigned_N
确保输出在[0... N-1]
中。请注意,
% some_pos_int_N
输出在(-N ...N)
范围内,因此建议使用 unsigned math来避免'-'
符号。字符串
可能还想用
"%u"
应该some_unsigned_N
靠近INT_MAX
。0aydgbwb2#
其他几个选项:
abort()
。这样做的优点是完全标准化,无论输入数据是否有效,都不会生成错误的字符串。它实际上可能比计算余数更快。字符串
unreachable()
(从C23开始)或__builtin_unreachable()
(GCC扩展)告诉编译器这些值在范围内。这没有开销,但传递无效日期不仅会生成意外的字符串;它将是未定义行为。如果您在日期验证之前意外地调用了作为日志代码一部分的函数,那么日期验证可能会被优化掉。型
型