Компиляция одной и той же программы на разных системах дает разные результаты? - PullRequest
0 голосов
/ 29 августа 2018

Вот программа:

#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

У меня также есть 2 машины под управлением Arch linux :

Ноутбук Linux 4.15.7-1-ARCH # 1 ПРЕДСТАВЛЕНИЕ SMP Ср 28 февраля 19:01:57 UTC 2018 x86_64 GNU / Linux

Хранилище Linux 4.14.66-1-ARCH # 1 SMP Сб 25 августа 01:09:50 UTC 2018 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

Почему есть разница?

UPDATE


Кажется, все комментарии касаются того, как исправить ошибку. Прежде всего, вы должны знать, что это свернутая программа. Дата, которую я создаю, сравнивается с другой, которую я не создал. Отсюда и формат мм / дд / гг. Кроме того, большинство функций, для которых в имени есть «n» (strncpy, snprintf ...), предназначены для случаев, когда вы не знаете данные или они создаются пользователем. Я смотрю на это как на ленивое программирование, потому что вы не знаете о данных, с которыми работаете. Кроме того, я точно знаю, что эта программа не будет использоваться в течение 82 лет.

Нет, мой вопрос касается разницы в результатах компиляции.

Ответы [ 3 ]

0 голосов
/ 29 августа 2018

Второй компилятор - твой друг.

Он правильно предупредил

Вывод '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);
0 голосов
/ 29 августа 2018

Три вещи:

  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. Если вы просто должны напечатать старомодный двухзначный год до 2000 года, вы можете использовать tm.tm_year % 100.

0 голосов
/ 29 августа 2018

Как сказал Осирис, 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");
...