在十六进制字面量后保护 "U" 后缀
Defending "U" suffix after Hex literals
我和我的同事就十六进制表示的文字后的 U
后缀存在一些争论。请注意,这不是关于此后缀的含义或它的作用的问题。我在这里找到了其中的几个主题,但我还没有找到问题的答案。
一些背景资料:
我们正在努力制定一套我们都同意的规则,从那时起将其作为我们的风格。我们有一份 2004 Misra C 规则的副本,并决定以此为起点。我们对完全符合 Misra C 不感兴趣;我们正在挑选我们认为最能提高效率和稳健性的规则。
上述准则中的规则 10.6 规定:
A “U” suffix shall be applied to all constants of unsigned type.
我个人认为这是一个很好的规则。它需要很少的努力,看起来比显式转换更好,并且更明确地显示了常量的意图。对我来说,将它用于 all 无符号常量是有意义的,而不仅仅是数字,因为通过允许异常不会执行规则,特别是对于常量的常用表示。
但是我的同事觉得十六进制表示不需要后缀。主要是因为我们几乎只用它来设置微控制器寄存器,而将寄存器设置为十六进制常量时,符号无关紧要。
我的问题
我的问题不是关于谁对谁错的问题。它是关于找出是否存在后缀的缺失或存在会改变操作结果的情况。有没有这样的情况,还是一致性的问题?
编辑:澄清;特别是关于通过为它们分配十六进制值来设置微控制器寄存器。在这种情况下,后缀会有所不同吗?我感觉不会例如,Freescale Processor Expert 将所有寄存器分配生成为无符号。
向所有十六进制常量附加 U
后缀使它们如您已经提到的那样无符号。当这些常量与有符号值一起用于操作时,尤其是比较时,这可能会产生不良副作用。
这是一个病态的例子:
#define MY_INT_MAX 0x7FFFFFFFU // blindly applying the rule
if (-1 < MY_INT_MAX) {
printf("OK\n");
} else {
printf("OOPS!\n");
}
signed/unsigned 转换的 C 规则被精确指定,但有点违反直觉,所以上面的代码确实会打印 OOPS
.
MISRA-C 规则非常精确,因为它指出 “U”后缀应应用于所有无符号类型的常量。 unsigned 具有深远的影响,实际上大多数常量不应该被视为无符号。
此外,C 标准在十进制和十六进制常量之间做了细微的区别:
- 如果十六进制常量的值可以用无符号整数类型表示,而不是类型
int
和更大的相同大小的有符号整数类型,则该常量被认为是无符号的。
这意味着在 32 位 2 的补码系统上,2147483648
是 long
或 long long
而 0x80000000
是 unsigned int
。在这种情况下,附加 U
后缀可能会使这一点更加明确,但避免潜在问题的真正预防措施是强制编译器完全拒绝 signed/unsigned 比较:gcc -Wall -Wextra -Werror
或 clang -Weverything -Werror
是救命恩人。
这是它的糟糕程度:
if (-1 < 0x8000) {
printf("OK\n");
} else {
printf("OOPS!\n");
}
上面的代码应该在 32 位系统上打印 OK
,在 16 位系统上打印 OOPS
。更糟糕的是,嵌入式项目使用过时的编译器仍然很常见,这些编译器甚至没有为这个问题实现标准语义。
对于您的具体问题,微处理器寄存器的定义值专门用于通过赋值设置它们(假设这些寄存器是内存映射的),根本不需要 U
后缀。寄存器左值应具有无符号类型,十六进制值将根据其值是有符号还是无符号,但操作将进行相同。设置有符号数或无符号数的操作码在您的目标架构和我见过的任何架构上都是相同的。
所有整数常量
追加 u/U
确保 整数常数 将是某种 无符号 类型。
没有 u/U
对于十进制常数,整数常数将是一些符号类型.
对于hexadecimal/octal-constant,整数常数将签名 或 unsigned 类型,取决于值和整数类型范围。
注意:所有 整数常数 都有正值。
// +-------- unary operator
// |+-+----- integer-constant
int x = -123;
absence or presence of the suffix changes the outcome of an operation?
什么时候这很重要?
对于各种表达式,需要控制数学的符号和宽度,这并不奇怪。
// Examples: assume 32-bit `unsigned`, `long`, 64-bit `long long`
// Bad signed int overflow (UB)
unsigned a = 4000 * 1000 * 1000;
// OK
unsigned b = 4000u * 1000 * 1000;
// undefined behavior
unsigned c = 1 << 31
// OK
unsigned d = 1u << 31
printf("Size %zu\n", sizeof(0xFFFFFFFF)); // 8 type is `long long`
printf("Size %zu\n", sizeof(0xFFFFFFFFu)); // 4 type is `unsigned`
// 2 ** 63
long long e = -9223372036854775808; // C99: bad "9223372036854775808" not representable
long long f = -9223372036854775807 - 1; // ok
long long g = -9223372036854775808u; // implementation defined behavior **
some_unsigned_type h_max = -1; OK, max value for the target type.
some_unsigned_type i_max = -1u; OK, but not max value for wide unsigned types
// when negating a negative `int`
unsigned j = 0 - INT_MIN; // typically int overflow or UB
unsigned k = 0u - INT_MIN; // Never UB
** 或引发实现定义的信号。
对于加载寄存器的特定问题,然后 U 将其设为无符号值,但无论编译器将 n 位字模式视为有符号值还是无符号值,它都会移动相同的位模式,假设没有任何可以传播 MSB 的大小扩展。可能重要的区别在于寄存器加载操作是否会根据有符号或无符号加载设置任何处理器条件标志。作为总体指南,如果处理器支持将常量存储到配置寄存器或内存地址,则加载外围寄存器不太可能设置处理器的 NEG 条件标志。加载连接到 ALU 的通用寄存器,它可以作为算术运算的目标,例如加法或减法, 可能 在加载时设置负标志,例如尾随的 "branch (if) negative" 操作码将执行该分支。您需要检查处理器的参考资料以确保。小型指令集处理器往往只有一个加载寄存器指令,而较大的指令集更有可能具有加载指令的加载无符号变体,该变体不会在处理器的标志中设置 NEG 位,但再次检查处理器的引用.如果您无权访问处理器的勘误表(boo-boo 列表)并且需要特定的标志状态。当优化编译器使用内联汇编指令和其他不常见的情况重新安排代码时,所有这些只会出现。检查生成汇编代码,在需要时关闭模块的部分或全部编译器优化等
我和我的同事就十六进制表示的文字后的 U
后缀存在一些争论。请注意,这不是关于此后缀的含义或它的作用的问题。我在这里找到了其中的几个主题,但我还没有找到问题的答案。
一些背景资料:
我们正在努力制定一套我们都同意的规则,从那时起将其作为我们的风格。我们有一份 2004 Misra C 规则的副本,并决定以此为起点。我们对完全符合 Misra C 不感兴趣;我们正在挑选我们认为最能提高效率和稳健性的规则。
上述准则中的规则 10.6 规定:
A “U” suffix shall be applied to all constants of unsigned type.
我个人认为这是一个很好的规则。它需要很少的努力,看起来比显式转换更好,并且更明确地显示了常量的意图。对我来说,将它用于 all 无符号常量是有意义的,而不仅仅是数字,因为通过允许异常不会执行规则,特别是对于常量的常用表示。
但是我的同事觉得十六进制表示不需要后缀。主要是因为我们几乎只用它来设置微控制器寄存器,而将寄存器设置为十六进制常量时,符号无关紧要。
我的问题
我的问题不是关于谁对谁错的问题。它是关于找出是否存在后缀的缺失或存在会改变操作结果的情况。有没有这样的情况,还是一致性的问题?
编辑:澄清;特别是关于通过为它们分配十六进制值来设置微控制器寄存器。在这种情况下,后缀会有所不同吗?我感觉不会例如,Freescale Processor Expert 将所有寄存器分配生成为无符号。
向所有十六进制常量附加 U
后缀使它们如您已经提到的那样无符号。当这些常量与有符号值一起用于操作时,尤其是比较时,这可能会产生不良副作用。
这是一个病态的例子:
#define MY_INT_MAX 0x7FFFFFFFU // blindly applying the rule
if (-1 < MY_INT_MAX) {
printf("OK\n");
} else {
printf("OOPS!\n");
}
signed/unsigned 转换的 C 规则被精确指定,但有点违反直觉,所以上面的代码确实会打印 OOPS
.
MISRA-C 规则非常精确,因为它指出 “U”后缀应应用于所有无符号类型的常量。 unsigned 具有深远的影响,实际上大多数常量不应该被视为无符号。
此外,C 标准在十进制和十六进制常量之间做了细微的区别:
- 如果十六进制常量的值可以用无符号整数类型表示,而不是类型
int
和更大的相同大小的有符号整数类型,则该常量被认为是无符号的。
这意味着在 32 位 2 的补码系统上,2147483648
是 long
或 long long
而 0x80000000
是 unsigned int
。在这种情况下,附加 U
后缀可能会使这一点更加明确,但避免潜在问题的真正预防措施是强制编译器完全拒绝 signed/unsigned 比较:gcc -Wall -Wextra -Werror
或 clang -Weverything -Werror
是救命恩人。
这是它的糟糕程度:
if (-1 < 0x8000) {
printf("OK\n");
} else {
printf("OOPS!\n");
}
上面的代码应该在 32 位系统上打印 OK
,在 16 位系统上打印 OOPS
。更糟糕的是,嵌入式项目使用过时的编译器仍然很常见,这些编译器甚至没有为这个问题实现标准语义。
对于您的具体问题,微处理器寄存器的定义值专门用于通过赋值设置它们(假设这些寄存器是内存映射的),根本不需要 U
后缀。寄存器左值应具有无符号类型,十六进制值将根据其值是有符号还是无符号,但操作将进行相同。设置有符号数或无符号数的操作码在您的目标架构和我见过的任何架构上都是相同的。
所有整数常量
追加 u/U
确保 整数常数 将是某种 无符号 类型。
没有 u/U
对于十进制常数,整数常数将是一些符号类型.
对于hexadecimal/octal-constant,整数常数将签名 或 unsigned 类型,取决于值和整数类型范围。
注意:所有 整数常数 都有正值。
// +-------- unary operator
// |+-+----- integer-constant
int x = -123;
absence or presence of the suffix changes the outcome of an operation?
什么时候这很重要?
对于各种表达式,需要控制数学的符号和宽度,这并不奇怪。
// Examples: assume 32-bit `unsigned`, `long`, 64-bit `long long`
// Bad signed int overflow (UB)
unsigned a = 4000 * 1000 * 1000;
// OK
unsigned b = 4000u * 1000 * 1000;
// undefined behavior
unsigned c = 1 << 31
// OK
unsigned d = 1u << 31
printf("Size %zu\n", sizeof(0xFFFFFFFF)); // 8 type is `long long`
printf("Size %zu\n", sizeof(0xFFFFFFFFu)); // 4 type is `unsigned`
// 2 ** 63
long long e = -9223372036854775808; // C99: bad "9223372036854775808" not representable
long long f = -9223372036854775807 - 1; // ok
long long g = -9223372036854775808u; // implementation defined behavior **
some_unsigned_type h_max = -1; OK, max value for the target type.
some_unsigned_type i_max = -1u; OK, but not max value for wide unsigned types
// when negating a negative `int`
unsigned j = 0 - INT_MIN; // typically int overflow or UB
unsigned k = 0u - INT_MIN; // Never UB
** 或引发实现定义的信号。
对于加载寄存器的特定问题,然后 U 将其设为无符号值,但无论编译器将 n 位字模式视为有符号值还是无符号值,它都会移动相同的位模式,假设没有任何可以传播 MSB 的大小扩展。可能重要的区别在于寄存器加载操作是否会根据有符号或无符号加载设置任何处理器条件标志。作为总体指南,如果处理器支持将常量存储到配置寄存器或内存地址,则加载外围寄存器不太可能设置处理器的 NEG 条件标志。加载连接到 ALU 的通用寄存器,它可以作为算术运算的目标,例如加法或减法, 可能 在加载时设置负标志,例如尾随的 "branch (if) negative" 操作码将执行该分支。您需要检查处理器的参考资料以确保。小型指令集处理器往往只有一个加载寄存器指令,而较大的指令集更有可能具有加载指令的加载无符号变体,该变体不会在处理器的标志中设置 NEG 位,但再次检查处理器的引用.如果您无权访问处理器的勘误表(boo-boo 列表)并且需要特定的标志状态。当优化编译器使用内联汇编指令和其他不常见的情况重新安排代码时,所有这些只会出现。检查生成汇编代码,在需要时关闭模块的部分或全部编译器优化等