在不同的系统上编译相同的程序会产生不同的结果?

时间:2018-08-28 22:10:41

标签: c date gcc printf

这是程序:

#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年内不再使用。

不,我的问题必须处理编译结果中的差异。

3 个答案:

答案 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
}

跟随ISO-8601是约会的好主意。 @jarmod

需要类似

"%04d-%02d-%02d", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday);

答案 2 :(得分:-1)

三件事:

  1. 定义#define DATE_SIZE 10太小了。只需将其设置为100。这些天的内存很便宜,而且生活太短了,无法花时间去尝试确定10是否足够大或者您是否需要11,然后在错误地选择10后追逐错误,但事实证明确实如此毕竟是11。

  2. 请勿致电sprintf。改为致电snprintf(date, sizeof(date), ...)(或snprintf(date, DATE_SIZE, ...))。这样,您绝对不会溢出date数组。 (在您的代码中,您检查了sprintf的返回值似乎是在捕获这种错误,但是普通的sprintf并没有捕获到这种错误。)

  3. 请不要计算tm.tm_year - 100。它可以在今天使用,但18年前将无法使用,而从现在起82年前将无法使用,这听起来似乎并不重要,但这仍然是错误的。如果可以解决,请计算并打印tm.tm_year + 1900。如果您仅必须打印一个老式的Y2K两位数年份,则可以使用tm.tm_year % 100