G CC Магия оптимизации - PullRequest
0 голосов
/ 13 июля 2020

У меня есть небольшая программа C, в которой пара функций должна печатать текущую дату, но они не всегда это делают:

#include <stdio.h>
#include <time.h>
#include <string.h>

#define BUF_LEN 16

typedef struct {
    const char *year;
    const char *month;
    const char *day;
} date_s;

date_s string_to_date(char *buffer) {
    if (buffer) {
        const char *year = strtok(buffer, "/");
        const char *month = strtok(NULL, "/");
        const char *day = strtok(NULL, "/");

        printf("INFO 2: YEAR: %s, MONTH: %s, DAY: %s\n", year, month, day);
        return (date_s) {.year = year, .month = month, .day = day};
    } else {
        return (date_s) {.year = "0000", .month = "00", .day = "00"};
    }
}

date_s get_current_date(void) {
    char buffer[BUF_LEN] = {0};

    time_t t = time(NULL);
    const struct tm *tm = localtime(&t);
    strftime(buffer, BUF_LEN, "%Y/%m/%d", tm);
    printf("INFO 1: %s\n", buffer);

    return string_to_date(buffer);
}

void print_data1(const char *year, const char *month, const char *day) {
    printf("1.) YEAR: %s, MONTH: %s, DAY: %s\n", year, month, day);
}

void print_data2(date_s date) {
    printf("2.) YEAR: %s, MONTH: %s, DAY: %s\n",
           date.year,
           date.month,
           date.day);
}

int main(void) {
    date_s current_date = get_current_date();

    // This prints always the expected values.
    //date_s current_date =  {.year = "0000", .month = "00", .day = "00"};

    printf("INFO 3: YEAR: %s, MONTH: %s, DAY: %s\n",
           current_date.year,
           current_date.month,
           current_date.day);

    print_data2(current_date);
    print_data1(current_date.year, current_date.month, current_date.day);
    
    return 0;
}

Компиляция, как это делает не показывает ожидаемый результат:

  gcc -g -Wall -O0 -std=c99 -o example example.c
  gcc -g -Wall -O1 -std=c99 -o example example.c

Он показывает что-то вроде этого:

INFO 1: 2020/07/13
INFO 2: YEAR: 2020, MONTH: 07, DAY: 13
INFO 3: YEAR: 2020, MONTH: 07, DAY: 13
2.) YEAR: �8���, MONTH: , DAY: [����U
1.) YEAR: �8���, MONTH: , DAY: X8���

Компиляция, как это, показывает ожидаемый результат:

  gcc -g -Wall -O2 -std=c99 -o example example.c
  gcc -g -Wall -O3 -std=c99 -o example example.c

Результат:

INFO 1: 2020/07/13
INFO 2: YEAR: 2020, MONTH: 07, DAY: 13
INFO 3: YEAR: 2020, MONTH: 07, DAY: 13
2.) YEAR: 2020, MONTH: 07, DAY: 13
1.) YEAR: 2020, MONTH: 07, DAY: 13

Вопросы:

  • Почему код не показывает текущую дату в первых двух случаях? (-O0 и -O1)
  • Почему код работает в последних двух случаях? (-O2 и -O3)
  • Что я могу сделать, чтобы он работал всегда? Также независимо от уровня оптимизации g cc.

1 Ответ

2 голосов
/ 14 июля 2020

Проблема здесь:

return (date_s) {.year = year, .month = month, .day = day}; 

Ваша программа имеет неопределенное поведение, потому что структура date_s, возвращаемая string_to_date, инициализируется указателями, указывающими на массив с автоматическим c хранилищем, имеющим вышло за пределы к тому времени, когда указатели используются в main для печати даты.

В зависимости от параметров компилятора содержимое массива может быть изменено или не изменено последующим выполнением кода. Неопределенное поведение может вызвать ряд побочных эффектов, в том числе отсутствие видимого эффекта.

Вы можете решить эту проблему, выделив копии строк, которые нужно будет освободить, когда структура больше не будет использоваться:

#include <stdio.h>
#include <time.h>
#include <string.h>

#define BUF_LEN 16

typedef struct {
    char *year;
    char *month;
    char *day;
} date_s;

date_s string_to_date(const char *buffer) {
    if (buffer) {
        const char *year = buffer;
        int year_length = strcspn(year, "/");
        const char *month = year + year_length + (year[year_length] == '/');
        int month_length = strcspn(month, "/");
        const char *day = month + month_length + (month[month_length] == '/');

        printf("INFO 2: YEAR: %.*s, MONTH: %.*s, DAY: %s\n", 
               year_length, year, month_length, month, day);
        return (date_s) {
            .year = strndup(year, year_length),
            .month = strndup(month, month_length),
            .day = strdup(day)
        };
    } else {
        return (date_s) {
            .year = strup("0000"), .month = strdup("00"), .day = strdup("00")
        };
    }
}

date_s get_current_date(void) {
    char buffer[BUF_LEN] = { 0 };

    time_t t = time(NULL);
    const struct tm *tm = localtime(&t);
    strftime(buffer, BUF_LEN, "%Y/%m/%d", tm);
    printf("INFO 1: %s\n", buffer);

    return string_to_date(buffer);
}

void print_data1(const char *year, const char *month, const char *day) {
    printf("1.) YEAR: %s, MONTH: %s, DAY: %s\n", year, month, day);
}

void print_data2(date_s date) {
    printf("2.) YEAR: %s, MONTH: %s, DAY: %s\n",
           date.year,
           date.month,
           date.day);
}

int main(void) {
    date_s current_date = get_current_date();

    // This prints always the expected values.
    //date_s current_date =  {.year = "0000", .month = "00", .day = "00"};

    printf("INFO 3: YEAR: %s, MONTH: %s, DAY: %s\n",
           current_date.year,
           current_date.month,
           current_date.day);

    print_data2(current_date);
    print_data1(current_date.year, current_date.month, current_date.day);

    free(current_date.year);
    free(current_date.month);
    free(current_date.day);
    
    return 0;
}
...