uint8_t не переносится на 0 после достижения 255 не работает должным образом - PullRequest
9 голосов
/ 09 мая 2019

Я новичок в C-заголовках - stdint.h и inttypes.h. Я пробовал код, чтобы понять, как работает uint8_t. Но, похоже, возникла проблема.

Я объявил 4 uint8_t целых чисел с граничными значениями 0, 255, 256, -1 соответственно и выполнил некоторые простые арифметические операции над ним. Я сделал это, так как хотел знать, что генерирует ошибки / предупреждения c-компилятор (я использую gcc 5.4.0 в linux). А если нет, мне было интересно узнать, как выглядит результат. Код приведен ниже.

#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>

int main() {
        int a = 10;
        printf("%d\n", a++);
        printf("%d\n\n", a);

        // 8 bit unsigned integer -> range[0, 255]
        uint8_t ua81=0, ua82=255, ua83=256, ua84=-1;
        printf("--------STDINT.H----uint8_t--DEMO--------\nua81 = %" PRIu8 "\n", ua81);
        printf("ua82 = %" PRIu8 "\nua83 = %" PRIu8 "\nua84 = %" PRIu8 "\n\n", ua82, ua83, ua84);
        printf("ua81+1 = %" PRIu8 "\nua82-3 = %" PRIu8 "\nua83-4+7 = %" PRIu8 "\nua84-1+20 = %" PRIu8 "\n----------\n\n", ua81+1, ua82-3, ua83-4+7, ua84-1+20);

        return 0;
}

Вывод этого кода следующий:

vagrant@ubuntu-xenial:~/Documents/Coding Practice/p_c$ vi stdint_h.c
vagrant@ubuntu-xenial:~/Documents/Coding Practice/p_c$ gcc -Wall stdint_h.c -o a
stdint_h.c: In function ‘main’:
stdint_h.c:11:33: warning: large integer implicitly truncated to unsigned type [-Woverflow]
  uint8_t ua81=0, ua82=255, ua83=256, ua84=-1;
                                 ^
vagrant@ubuntu-xenial:~/Documents/Coding Practice/p_c$ ./a
10
11

--------STDINT.H----uint8_t--DEMO--------
ua81 = 0
ua82 = 255
ua83 = 0
ua84 = 255

ua81+1 = 1
ua82-3 = 252
ua83-4+7 = 3
ua84-1+20 = 274
----------

Как упоминалось ранее, я использую Linux-машину для этого с компилятором gcc 5.4.0.

vagrant@ubuntu-xenial:~/Documents/Coding Practice/p_c$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Я не могу понять, почему для переменной ua84 значение не переходит на 0 после достижения 255, в то время как то же самое работает для ua83. Я думаю, что значение, учитывая опрокидывание после 255, ua84 должно было быть 18.

Мне кажется, это как-то связано с неявным усечением , упомянутым в warning, который был сгенерирован для переменной ua83 во время компиляции. Я не знаю точно, что не так с этим. Я также хотел бы знать, как поступить так, что я все еще хочу продолжать использовать uint8_t.

Ответы [ 3 ]

10 голосов
/ 09 мая 2019

Из-за неявных повышений ua84-1+20 будет int и будет иметь значение 274. Вы должны явным образом преобразовать результат в uint8_t, чтобы он стал 18.

printf не обладает достаточной магией для преобразования аргументов в зависимости от формата (*) . Он только ожидает, что получит то, что ему нужно, и просто извлечет переданные параметры. Поэтому здесь вы должны написать:

    printf("ua81+1 = %" PRIu8 "\nua82-3 = %" PRIu8 "\nua83-4+7 = %" PRIu8 "\nua84-1+20 = %"
            PRIu8 "\n----------\n\n", (uint8_t)(ua81+1), (uint8_t)(ua82-3), (uint8_t)(ua83-4+7),
            (uint8_t)(ua84-1+20));

(*) Точнее, проблема в спецификаторе формата. В пункте 7.21.6.1 в функции fprintf §7 черновик n1570 для C11 написано:

чч Указывает, что следующий спецификатор преобразования d, i, o, u, x или X применяется к аргумент char со знаком или без знака (аргумент будет иметь был повышен в соответствии с целочисленными акциями, но его значение должно быть перед печатью преобразуется в подписанный или неподписанный символ ); ...

Итак, если вы явно использовали %hhu, вы бы получили 18 (даже если 274 были пройдены). Но, к сожалению, макросы из inttype.h требуются только для правильного отображения значения ожидаемого диапазона, поэтому многие реализации переводят PRIu8 как u (спасибо Эрику Постпишилу за то, что это заметил).

2 голосов
/ 09 мая 2019

printf семейство функций очень опасно и нестабильно, с несуществующей типобезопасностью. Поэтому, если вы предоставляете аргумент, который не относится к типу, который вы указали в спецификаторе формата, вы вызываете неопределенное поведение. Об этом вам скажут программисты библиотеки.

Выражение ua84-1+20 приводит к неявному продвижению типа до int, см. Правила неявного продвижения по типу . Таким образом, вы врете printf с PRIu8, говоря ему ожидать uint8_t, пока вы передаете int.

Это можно исправить, преобразовав обратно в ожидаемый тип: (uint8_t)(ua84-1+20), который будет печатать 18, как и ожидалось.

Примечательно, что все функции типа variadic, такие как printf, также имеют похожий тип неявного продвижения типов, известный как «продвижения аргументов по умолчанию». Они фактически преобразуют параметр в int независимо от того, что вы делаете. Но библиотека ожидает, что это продвижение произойдет, поэтому, когда вы набираете PRIu8, а затем (uint8_t)whatever, они ожидают, что будут иметь дело с (int)(uint8_t)whatever внутри.

Однако, это очень подозрительно, что библиотека печатает 274, UB или нет. Это говорит о том, что реализация printf фактически не преобразует параметр обратно в uint8_t внутри. Как библиотека может это оправдать, это хороший вопрос, потому что, как мы видим, это приводит к ошибкам и менее надежному коду.

2 голосов
/ 09 мая 2019

Выражение ua84-1+20 приводит к тому, что переменная ua84 претерпевает то, что называется целочисленное продвижение : оно превращается в int, и все выражение затем оценивается в int.

Затем он печатается как беззнаковое целое - вы, скорее всего, увидите, что PRIu8 определен как "u", что, конечно, ожидает unsigned int (И получается, даже если передано uint8_t из-за того, как работают переменные их целочисленные аргументы повышаются (uint8_t значения повышаются до int, а затем это обрабатывается как unsigned int на printf())).

Подробности: https://en.cppreference.com/w/c/language/conversion

...