我什么时候应该在 C 中使用 UINT32_C(), INT32_C(),... 宏?
When should I use UINT32_C(), INT32_C(),... macros in 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
。但是 0
和 1
应该是整数,编译器应该自动将其转换为正确的类型。如果我在示例中使用它,代码将更易于阅读:
uint64_t i;
for (i = 0; i < 10; i++) { ... }
那么什么时候应该使用像 UINT32_C()
这样的固定长度常量宏,什么时候应该把这项工作留给编译器(我正在使用 GCC)?如果我用 MISRA C 编写代码会怎么样?
它用于在上下文不会导致编译器将其转换为正确大小的情况下使用较小的整数文字。
我在嵌入式平台上工作过,其中 int
是 16 位,long
是 32 位。如果您尝试编写可移植代码以在具有 16 位或 32 位 int
类型的平台上工作,并且想要将 32 位 "unsigned integer literal" 传递给可变参数函数,您需要演员:
#define BAUDRATE UINT32_C(38400)
printf("Set baudrate to %" PRIu32 "\n", BAUDRATE);
在 16 位平台上,强制转换创建 38400UL
,而在 32 位平台上仅创建 38400U
。这些将匹配 "lu"
或 "u"
的 PRIu32
宏。
我认为当 X
是整数文字时,大多数编译器会为 (uint32_t) X
生成与 UINT32_C(X)
相同的代码,但早期编译器可能并非如此。
根据经验,您应该在文字类型很重要时使用它们。有两件事需要考虑:大小和签名。
关于尺码:
int
类型由最大 32767
的 C 标准值保证。由于您无法获得类型小于 int
的整数文字,所有小于 32767
的值都不需要使用宏。如果您需要更大的值,那么文字的类型就开始变得重要,使用这些宏是个好主意。
关于签名:
没有后缀的整数文字通常是有符号类型。这是潜在的危险,因为它可能会在隐式类型提升期间导致各种细微的错误。例如 (my_uint8_t + 1) << 31
会在 32 位系统上导致未定义的行为错误,而 (my_uint8_t + 1u) << 31
不会。
这就是为什么 MISRA 有一条规则规定,如果打算使用无符号类型,所有整数文字都应该有一个 u
/U
后缀。因此,在我上面的示例中,您可以使用 my_uint8_t + UINT32_C(1)
,但您也可以使用 1u
,这可能是最易读的。对于 MISRA,两者都应该没问题。
至于为什么stdbool.h定义true/false为1/0,是因为标准有明确规定。出于向后兼容的原因,C 中的布尔条件仍然使用 int
类型,而不是 C++ 中的 bool
类型。
然而,将布尔条件视为 C 具有真正的布尔类型被认为是一种很好的风格。 MISRA-C:2012有一整套关于这个概念的规则,叫做essentially boolean类型。这可以在静态分析期间提供更好的类型安全性,还可以防止各种错误。
我在我的项目中切换到定长整数类型主要是因为它们帮助我在使用它们时更清楚地考虑整数大小。通过 #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
。但是 0
和 1
应该是整数,编译器应该自动将其转换为正确的类型。如果我在示例中使用它,代码将更易于阅读:
uint64_t i;
for (i = 0; i < 10; i++) { ... }
那么什么时候应该使用像 UINT32_C()
这样的固定长度常量宏,什么时候应该把这项工作留给编译器(我正在使用 GCC)?如果我用 MISRA C 编写代码会怎么样?
它用于在上下文不会导致编译器将其转换为正确大小的情况下使用较小的整数文字。
我在嵌入式平台上工作过,其中 int
是 16 位,long
是 32 位。如果您尝试编写可移植代码以在具有 16 位或 32 位 int
类型的平台上工作,并且想要将 32 位 "unsigned integer literal" 传递给可变参数函数,您需要演员:
#define BAUDRATE UINT32_C(38400)
printf("Set baudrate to %" PRIu32 "\n", BAUDRATE);
在 16 位平台上,强制转换创建 38400UL
,而在 32 位平台上仅创建 38400U
。这些将匹配 "lu"
或 "u"
的 PRIu32
宏。
我认为当 X
是整数文字时,大多数编译器会为 (uint32_t) X
生成与 UINT32_C(X)
相同的代码,但早期编译器可能并非如此。
根据经验,您应该在文字类型很重要时使用它们。有两件事需要考虑:大小和签名。
关于尺码:
int
类型由最大 32767
的 C 标准值保证。由于您无法获得类型小于 int
的整数文字,所有小于 32767
的值都不需要使用宏。如果您需要更大的值,那么文字的类型就开始变得重要,使用这些宏是个好主意。
关于签名:
没有后缀的整数文字通常是有符号类型。这是潜在的危险,因为它可能会在隐式类型提升期间导致各种细微的错误。例如 (my_uint8_t + 1) << 31
会在 32 位系统上导致未定义的行为错误,而 (my_uint8_t + 1u) << 31
不会。
这就是为什么 MISRA 有一条规则规定,如果打算使用无符号类型,所有整数文字都应该有一个 u
/U
后缀。因此,在我上面的示例中,您可以使用 my_uint8_t + UINT32_C(1)
,但您也可以使用 1u
,这可能是最易读的。对于 MISRA,两者都应该没问题。
至于为什么stdbool.h定义true/false为1/0,是因为标准有明确规定。出于向后兼容的原因,C 中的布尔条件仍然使用 int
类型,而不是 C++ 中的 bool
类型。
然而,将布尔条件视为 C 具有真正的布尔类型被认为是一种很好的风格。 MISRA-C:2012有一整套关于这个概念的规则,叫做essentially boolean类型。这可以在静态分析期间提供更好的类型安全性,还可以防止各种错误。