【问题标题】:Compiling the same program on different systems produces different results?在不同的系统上编译相同的程序会产生不同的结果?
【发布时间】:2019-02-03 14:48:58
【问题描述】:

这是程序:

#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 2018 年 2 月 28 日星期三 19:01:57 UTC x86_64 GNU/Linux

Linux 存储 4.14.66-1-ARCH #1 SMP 2018 年 8 月 25 日星期六 01:09:50 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

为什么会有差异?

更新


所有 cmets 似乎都在处理如何修复错误。首先,您需要知道这是一个最小化的程序。我创建的日期与我没有创建的日期进行比较。因此采用 mm/dd/yy 格式。此外,大多数在名称中包含“n”的函数(strncpy、snprintf...)都适用于您不知道数据或者它是用户生成的数据。我将其视为惰性编程,因为您不知道您正在使用的数据。另外,我知道这个程序在 82 年内不会使用。

不,我的问题要处理编译结果的差异。

【问题讨论】:

  • 只是警告您"%d/%d/%d" 可能包含超过 11 个字节,因此可能会溢出。
  • 正如奥西里斯所说;还建议使用snprintf() :-)
  • 嗯,再想一想,即使是 16 位 int 类型的系统溢出缓冲区也没有问题。好奇为什么它只在一个系统上发出警告。不同版本的 GCC?
  • %d/%d/%d(月/日/年)很少能很好地表示日期。尝试根据 ISO 8601 使用 YYYY-MM-DD。另请参阅 xkcd.com/1179
  • 如果您的问题是关于编译结果的差异,请附上编译器版本 (gcc -v)。

标签: c date gcc printf


【解决方案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

【讨论】:

    【解决方案2】:

    第二个编译器是你的朋友。

    它已正确警告

    '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);
    

    【讨论】:

      【解决方案3】:

      正如 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");
      

      【讨论】:

      • 出于某种脑残原因,我认为这是文件名>_>
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-07-08
      • 2019-06-30
      • 1970-01-01
      • 2020-04-02
      • 1970-01-01
      • 1970-01-01
      • 2017-01-23
      相关资源
      最近更新 更多