这是程序:
#include <stdio.h>
#include <libgen.h>
#include <stdlib.h>
#include <time.h>
#define DATE_SIZE 10
// Declare global variables.
char *program_name = NULL;
int main (int argc, char *argv[])
{
// Declare variables.
time_t t = time(NULL);
struct tm tm = *localtime(&t);
char date[DATE_SIZE + 1] = {0};
// Store today's date in a string for comparison.
if(sprintf(date, "%d/%d/%d", tm.tm_mon + 1, tm.tm_mday, tm.tm_year - 100) < 0)
{
fprintf(stderr, "%s: main error: sprintf failed.\n", program_name);
exit(EXIT_FAILURE);
}
// Print date to user.
printf("Date: %s\n", date);
// Exit gracefully.
exit(EXIT_SUCCESS);
}
使用以下代码进行编译:
gcc -Wall -Werror -O3 -o program program.c
我也有两台同时运行 Arch linux 的计算机:
Linux笔记本电脑4.15.7-1-ARCH#1 SMP PREEMPT UTC Feb 28 19:01:57 UTC 2018 x86_64 GNU / Linux
Linux存储4.14.66-1-ARCH#1 SMP 2018年8月25日星期六UTC armv6l GNU / Linux
当我在笔记本电脑上编译时,它很干净并且可以完美运行。在存储服务器上编译时,出现以下错误:
program.c: In function 'main':
program.c:20:5: error: '/' directive writing 1 byte into a region of size between 0 and 10 [-Werror=format-overflow=]
if(sprintf(date, "%d/%d/%d", tm.tm_mon + 1, tm.tm_mday, tm.tm_year - 100) < 0)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
program.c:20:5: note: directive argument in the range [-2147483648, 2147483547]
program.c:20:5: note: 'sprintf' output between 6 and 36 bytes into a destination of size 11
cc1: all warnings being treated as errors
为什么有区别?
更新
所有注释似乎都在解决错误。首先,您需要知道这是一个最小化的程序。将我创建的日期与未创建的日期进行比较。因此,mm / dd / yy格式。同样,大多数在名称中包含“ n”的函数(strncpy,snprintf ...)适用于您不知道数据或用户生成的数据。我将其视为惰性编程,因为您不知道要使用的数据。另外,我知道该程序将在82年内不再使用。
不,我的问题必须处理编译结果中的差异。
答案 0 :(得分:-1)
如奥西里斯(Osiris)所说,sprintf()
可能在DATE_SIZE+1
中写入了超过date
个字节。
如果要存储文件名,我建议使用FILENAME_MAX
作为缓冲区的大小-保证足够足够大。
此外,使用snprintf()
限制写入缓冲区的字符数。这样可以避免内存管理问题,在其他情况下,这可能会导致安全漏洞。
检查snprintf()
的返回值并确认已写入整个字符串(即您的date
足够大。
char date[FILENAME_MAX];
int rv = sprintf(date, sizeof(date), "%d/%d/%d",
tm.tm_mon + 1, tm.tm_mday, tm.tm_year - 100);
if (rv < 0) printf("encoding error\n");
if (rv == sizeof(date)) printf("not big enough\n");
答案 1 :(得分:-1)
第二个编译器是你的朋友。
已正确警告
'sprintf'输出6到36字节之间的数据到目标大小为11的目标
如果tm.tm_year
较大,则缓冲区将溢出。
编码这样一个不足为11的缓冲区几乎没有什么好处。
int
最多可以包含ceil(log10(INT_MAX))
位数字。加上一个符号,不超过:
// sign v--- value bits -------v *log10(2) round
#define INT_DEC_LEN (1 + (sizeof(int)*CHAR_BIT - 1)*302/100 + 1)
无论struct tm tm
的内容如何,都要考虑一个足够大的缓冲区。朋友之间的几个额外字节是什么?
struct tm tm = *localtime(&t);
#define DMY_FMT "%d/%d/%d"
#define DMY_SIZE (sizeof(DMY_FMT) + 3*INT_DEC_LEN + 1)
char date[DMY_SIZE] = {0};
if(sprintf(date, "%d/%d/%d", tm.tm_mon + 1, tm.tm_mday, tm.tm_year - 100) < 0)
代码也可以走snprintf()
路线。请注意,负值或较大的返回值表示存在问题。
int cnt = snprintf(date, sizeof data, "%d/%d/%d", tm.tm_mon + 1, tm.tm_mday, tm.tm_year - 100);
if (cnt < 0 || cnt >= sizeof data) {
// Handle error
}
需要类似
"%04d-%02d-%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);
答案 2 :(得分:-1)
三件事:
定义#define DATE_SIZE 10
太小了。只需将其设置为100。这些天的内存很便宜,而且生活太短了,无法花时间去尝试确定10是否足够大或者您是否需要11,然后在错误地选择10后追逐错误,但事实证明确实如此毕竟是11。
请勿致电sprintf
。改为致电snprintf(date, sizeof(date), ...)
(或snprintf(date, DATE_SIZE, ...)
)。这样,您绝对不会溢出date
数组。 (在您的代码中,您检查了sprintf
的返回值似乎是在捕获这种错误,但是普通的sprintf
并没有捕获到这种错误。)
请不要计算tm.tm_year - 100
。它可以在今天使用,但18年前将无法使用,而从现在起82年前将无法使用,这听起来似乎并不重要,但这仍然是错误的。如果可以解决,请计算并打印tm.tm_year + 1900
。如果您仅必须打印一个老式的Y2K两位数年份,则可以使用tm.tm_year % 100
。