Когда следует использовать макросы UINT32 C(), INT32 C(),... в C?

я переключился на целочисленные типы фиксированной длины в своих проектах в основном потому, что они помогают мне более четко думать о целочисленных размерах при их использовании. Включая их через #include <inttypes.h> также включает в себя кучу других макросов, таких как макросы печати PRIu32, PRIu64,...

чтобы назначить постоянное значение переменной фиксированной длины, я могу использовать макросы, такие как UINT32_C() и INT32_C(). Я начал использовать их всякий раз, когда назначал постоянное значение.

это приводит к коду, похожему на это:

uint64_t i;
for (i = UINT64_C(0); i < UINT64_C(10); i++) { ... }

теперь я видел несколько примеров, которые не позаботились об этом. Один из них stdbool.h включить файл:

#define bool    _Bool
#define false   0
#define true    1

bool имеет размер 1 байт на моей машине, поэтому он не похож на int. Но!--11--> и 1 должны быть целыми числами, которые должны быть автоматически преобразованы компилятором в правильный тип. Если бы я использовал это в своем примере, код было бы намного проще читать:

uint64_t i;
for (i = 0; i < 10; i++) { ... }

Итак, когда я должен использовать фиксированный длина постоянных макросов, таких как UINT32_C() и когда я должен оставить эту работу компилятору (я использую GCC)? Что, если я напишу код на MISRA C?

2 ответов


как правило, вы должны использовать их, когда тип литерала имеет значение. Нужно учитывать две вещи: размер и значимость.

размер:

An int тип гарантируется стандартными значениями C до 32767. Поскольку вы не можете получить целочисленный литерал с меньшим типом, чем int, все значения меньше 32767 Не нужно использовать макросы. Если вам нужны большие значения, то тип литерала начинает иметь значение и это хорошая идея, чтобы использовать эти макросы.

относительно signedness:

целые числа без знака обычно типа signed. Это потенциально опасно, так как это может вызвать всевозможные тонкие ошибки во время неявного продвижения типа. Например (my_uint8_t + 1) << 31 вызовет неопределенную ошибку поведения в 32-битной системе, в то время как (my_uint8_t + 1u) << 31 не будет.

вот почему у Мисры есть правило, гласящее, что все целочисленные литералы должны иметь u/U суффикс предполагается использовать неподписанные типы. Поэтому в моем примере выше вы можете использовать my_uint8_t + UINT32_C(1) но вы также можете использовать 1u, что, пожалуй, наиболее читаемо. Для Мисры подойдет и то, и другое.


что касается почему stdbool.h определяет true / false как 1/0, потому что стандарт явно говорит об этом. Логические условия в C по-прежнему использовать int тип, а не bool введите, как в C++, по причинам обратной совместимости.

однако считается хорошим стилем лечить логического условия, если c есть тип boolean. MISRA-C:2012 имеет целый набор правил, касающихся этой концепции, называется по существу boolean тип. Это может дать лучшую безопасность типа во время статического анализа и также предотвратить различные ошибки.


это для использования небольших целочисленных литералов, где контекст не приведет к тому, что компилятор приведет его к правильному размеру.

я работал на встроенной платформе, где int - это 16 бит и long - это 32 бита. Если вы пытались написать портативный код для работы на платформах с 16-битным или 32-битным int типы, и хотел передать 32-разрядная "беззнаковый целочисленный литерал" функции с переменным числом аргументов, ты нужен ролях:

#define BAUDRATE UINT32_C(38400)
printf("Set baudrate to %" PRIu32 "\n", BAUDRATE);

на 16-битной платформе, актерский состав создает 38400UL и на 32-битной платформе, просто 38400U. Они будут соответствовать PRIu32 макрос либо "lu" или "u".

я думаю, что большинство компиляторов будет генерировать идентичный код для (uint32_t) X по состоянию на UINT32_C(X), когда X является целочисленным литералом, но это может быть не так с ранними компиляторами.