如何摆脱 C 宏 -Wshift-count-overflow 警告
How to get rid of C macro -Wshift-count-overflow warning
/* --- PRINTF_BYTE_TO_BINARY macro's --- */
#define PRINTF_BINARY_PATTERN_INT8 " %c%c%c%c%c%c%c%c"
#define PRINTF_BYTE_TO_BINARY_INT8(i) \
(((i) & 0x80ll) ? '1' : '0'), \
(((i) & 0x40ll) ? '1' : '0'), \
(((i) & 0x20ll) ? '1' : '0'), \
(((i) & 0x10ll) ? '1' : '0'), \
(((i) & 0x08ll) ? '1' : '0'), \
(((i) & 0x04ll) ? '1' : '0'), \
(((i) & 0x02ll) ? '1' : '0'), \
(((i) & 0x01ll) ? '1' : '0')
#define PRINTF_BINARY_PATTERN_INT16 \
PRINTF_BINARY_PATTERN_INT8 PRINTF_BINARY_PATTERN_INT8
#define PRINTF_BYTE_TO_BINARY_INT16(i) \
PRINTF_BYTE_TO_BINARY_INT8((i) >> 8), PRINTF_BYTE_TO_BINARY_INT8(i)
#define PRINTF_BINARY_PATTERN_INT32 \
PRINTF_BINARY_PATTERN_INT16 PRINTF_BINARY_PATTERN_INT16
#define PRINTF_BYTE_TO_BINARY_INT32(i) \
PRINTF_BYTE_TO_BINARY_INT16((i) >> 16), PRINTF_BYTE_TO_BINARY_INT16(i)
#define PRINTF_BINARY_PATTERN_INT64 \
PRINTF_BINARY_PATTERN_INT32 PRINTF_BINARY_PATTERN_INT32
#define PRINTF_BYTE_TO_BINARY_INT64(i) \
PRINTF_BYTE_TO_BINARY_INT32((i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)
/* --- end macros --- */
我从 BitField.cpp 得到了上面的代码。
当我使用cc编译器编译代码时,出现如下警告。
警告:移位计数 >= 类型的宽度 [-Wshift-count-overflow]
PRINTF_BYTE_TO_BINARY_INT64(n));
警告由“PRINTF_BYTE_TO_BINARY_INT32((i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)”生成。如果将“>> 32”更改为“>> 31”或任何小于 32 的数字,警告就会消失。但是,我需要右移32位,结果是正确的。只是警告很烦人。我想知道是否有某种方法可以修复代码,以便警告消失。
除了关闭和忽略警告之外,还有其他办法绕过这个警告吗?
只需添加演员表。它会让这个宏变得更糟。
/* --- PRINTF_BYTE_TO_BINARY 宏的 --- */
#define PRINTF_BINARY_PATTERN_INT8 " %c%c%c%c%c%c%c%c"
#define PRINTF_BYTE_TO_BINARY_INT8(i) \
(((i) & 0x80ll) ? '1' : '0'), \
(((i) & 0x40ll) ? '1' : '0'), \
(((i) & 0x20ll) ? '1' : '0'), \
(((i) & 0x10ll) ? '1' : '0'), \
(((i) & 0x08ll) ? '1' : '0'), \
(((i) & 0x04ll) ? '1' : '0'), \
(((i) & 0x02ll) ? '1' : '0'), \
(((i) & 0x01ll) ? '1' : '0')
#define PRINTF_BINARY_PATTERN_INT16 \
PRINTF_BINARY_PATTERN_INT8 PRINTF_BINARY_PATTERN_INT8
#define PRINTF_BYTE_TO_BINARY_INT16(i) \
PRINTF_BYTE_TO_BINARY_INT8((int16_t)(i) >> 8), PRINTF_BYTE_TO_BINARY_INT8(i)
#define PRINTF_BINARY_PATTERN_INT32 \
PRINTF_BINARY_PATTERN_INT16 PRINTF_BINARY_PATTERN_INT16
#define PRINTF_BYTE_TO_BINARY_INT32(i) \
PRINTF_BYTE_TO_BINARY_INT16((int32_t)(i) >> 16), PRINTF_BYTE_TO_BINARY_INT16(i)
#define PRINTF_BINARY_PATTERN_INT64 \
PRINTF_BINARY_PATTERN_INT32 PRINTF_BINARY_PATTERN_INT32
#define PRINTF_BYTE_TO_BINARY_INT64(i) \
PRINTF_BYTE_TO_BINARY_INT32((int64_t)(i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)
这些宏存在以下问题:
- 他们正在对签名类型进行转换。有符号类型可能包含负值,然后右移的结果是 implementation-defined:它可以是算术或逻辑移位,具体取决于编译器。使用原始二进制文件时始终使用无符号类型。
- 将 32 位类型移动 32 位或更多位没有意义,因此出现警告。这在形式上是一个未定义的行为错误——C 标准说“如果右操作数的值为负数或大于或等于提升的左操作数的宽度,则行为未定义。”
- 宏像任何东西一样效率低下,因为它归结为
?:
检查的 8 个分支,对于某些在编译时可能是 pre-computed 的东西。没有必要打印这个 character-by-character.
- 宏具有 non-existent 类型安全性,就像任何旧的 C 宏一样。
为了修复算法本身,8 位 look-up table 将是理想的,但意味着存储 256 个字符串。合理的速度与大小折衷通常是使用 4 位半字节 look-up 代替。示例:
static const char* BINSTR_NIBBLE [16] =
{
[0x00] = "0000", [0x01] = "0001", [0x02] = "0010", [0x03] = "0011",
[0x04] = "0100", [0x05] = "0101", [0x06] = "0110", [0x07] = "0111",
[0x08] = "1000", [0x09] = "1001", [0x0A] = "1010", [0x0B] = "1011",
[0x0C] = "1100", [0x0D] = "1101", [0x0E] = "1110", [0x0F] = "1111",
};
如果我们也想在它们之间打印一个 space,那么半字节可能是一个不错的选择,这是习惯。
可以使用 C 标准 _Generic
并通过强制转换为保证不是小整数类型的类型(有隐式提升为有符号的风险)来修复类型安全和 icky 签名类型。
解决上述所有问题的完整示例:
#include <stdio.h>
#include <stdint.h>
static const char* BINSTR_NIBBLE [16] =
{
[0x00] = "0000", [0x01] = "0001", [0x02] = "0010", [0x03] = "0011",
[0x04] = "0100", [0x05] = "0101", [0x06] = "0110", [0x07] = "0111",
[0x08] = "1000", [0x09] = "1001", [0x0A] = "1010", [0x0B] = "1011",
[0x0C] = "1100", [0x0D] = "1101", [0x0E] = "1110", [0x0F] = "1111",
};
#define print_bin(i) _Generic((i), \
int8_t: printf( "%s %s\n", \
BINSTR_NIBBLE[(uint32_t)(i) >> 4 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 0 & 0xFu] ), \
uint8_t: printf( "%s %s\n", \
BINSTR_NIBBLE[(uint32_t)(i) >> 4 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 0 & 0xFu] ), \
int16_t: printf( "%s %s %s %s\n", \
BINSTR_NIBBLE[(uint32_t)(i) >> 12 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 8 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 4 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 0 & 0xFu] ), \
uint16_t: printf( "%s %s %s %s\n", \
BINSTR_NIBBLE[(uint32_t)(i) >> 12 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 8 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 4 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 0 & 0xFu] ) )
int main (void)
{
uint8_t u8 = 0xAB;
int16_t i16 = -1;
print_bin(u8);
print_bin(i16);
return 0;
}
输出
1010 1011
1111 1111 1111 1111
等等 - 扩展到 32 或 64 位类型的支持应该是微不足道的。这也可以进一步优化,但它已经比原来的宏快了好几倍,也更安全了。上面的 gcc x86 反汇编归结为 branch-free 字符串铲除(使用字符串池以节省内存):
.LC0:
.string "1011"
.LC1:
.string "1010"
.LC2:
.string "%s %s\n"
.LC3:
.string "1111"
.LC4:
.string "%s %s %s %s\n"
main:
sub rsp, 8
mov edx, OFFSET FLAT:.LC0
mov esi, OFFSET FLAT:.LC1
xor eax, eax
mov edi, OFFSET FLAT:.LC2
call printf
mov r8d, OFFSET FLAT:.LC3
mov edi, OFFSET FLAT:.LC4
xor eax, eax
mov rcx, r8
mov rdx, r8
mov rsi, r8
call printf
xor eax, eax
add rsp, 8
ret
/* --- PRINTF_BYTE_TO_BINARY macro's --- */
#define PRINTF_BINARY_PATTERN_INT8 " %c%c%c%c%c%c%c%c"
#define PRINTF_BYTE_TO_BINARY_INT8(i) \
(((i) & 0x80ll) ? '1' : '0'), \
(((i) & 0x40ll) ? '1' : '0'), \
(((i) & 0x20ll) ? '1' : '0'), \
(((i) & 0x10ll) ? '1' : '0'), \
(((i) & 0x08ll) ? '1' : '0'), \
(((i) & 0x04ll) ? '1' : '0'), \
(((i) & 0x02ll) ? '1' : '0'), \
(((i) & 0x01ll) ? '1' : '0')
#define PRINTF_BINARY_PATTERN_INT16 \
PRINTF_BINARY_PATTERN_INT8 PRINTF_BINARY_PATTERN_INT8
#define PRINTF_BYTE_TO_BINARY_INT16(i) \
PRINTF_BYTE_TO_BINARY_INT8((i) >> 8), PRINTF_BYTE_TO_BINARY_INT8(i)
#define PRINTF_BINARY_PATTERN_INT32 \
PRINTF_BINARY_PATTERN_INT16 PRINTF_BINARY_PATTERN_INT16
#define PRINTF_BYTE_TO_BINARY_INT32(i) \
PRINTF_BYTE_TO_BINARY_INT16((i) >> 16), PRINTF_BYTE_TO_BINARY_INT16(i)
#define PRINTF_BINARY_PATTERN_INT64 \
PRINTF_BINARY_PATTERN_INT32 PRINTF_BINARY_PATTERN_INT32
#define PRINTF_BYTE_TO_BINARY_INT64(i) \
PRINTF_BYTE_TO_BINARY_INT32((i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)
/* --- end macros --- */
我从 BitField.cpp 得到了上面的代码。
当我使用cc编译器编译代码时,出现如下警告。
警告:移位计数 >= 类型的宽度 [-Wshift-count-overflow] PRINTF_BYTE_TO_BINARY_INT64(n));
警告由“PRINTF_BYTE_TO_BINARY_INT32((i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)”生成。如果将“>> 32”更改为“>> 31”或任何小于 32 的数字,警告就会消失。但是,我需要右移32位,结果是正确的。只是警告很烦人。我想知道是否有某种方法可以修复代码,以便警告消失。
除了关闭和忽略警告之外,还有其他办法绕过这个警告吗?
只需添加演员表。它会让这个宏变得更糟。
/* --- PRINTF_BYTE_TO_BINARY 宏的 --- */
#define PRINTF_BINARY_PATTERN_INT8 " %c%c%c%c%c%c%c%c"
#define PRINTF_BYTE_TO_BINARY_INT8(i) \
(((i) & 0x80ll) ? '1' : '0'), \
(((i) & 0x40ll) ? '1' : '0'), \
(((i) & 0x20ll) ? '1' : '0'), \
(((i) & 0x10ll) ? '1' : '0'), \
(((i) & 0x08ll) ? '1' : '0'), \
(((i) & 0x04ll) ? '1' : '0'), \
(((i) & 0x02ll) ? '1' : '0'), \
(((i) & 0x01ll) ? '1' : '0')
#define PRINTF_BINARY_PATTERN_INT16 \
PRINTF_BINARY_PATTERN_INT8 PRINTF_BINARY_PATTERN_INT8
#define PRINTF_BYTE_TO_BINARY_INT16(i) \
PRINTF_BYTE_TO_BINARY_INT8((int16_t)(i) >> 8), PRINTF_BYTE_TO_BINARY_INT8(i)
#define PRINTF_BINARY_PATTERN_INT32 \
PRINTF_BINARY_PATTERN_INT16 PRINTF_BINARY_PATTERN_INT16
#define PRINTF_BYTE_TO_BINARY_INT32(i) \
PRINTF_BYTE_TO_BINARY_INT16((int32_t)(i) >> 16), PRINTF_BYTE_TO_BINARY_INT16(i)
#define PRINTF_BINARY_PATTERN_INT64 \
PRINTF_BINARY_PATTERN_INT32 PRINTF_BINARY_PATTERN_INT32
#define PRINTF_BYTE_TO_BINARY_INT64(i) \
PRINTF_BYTE_TO_BINARY_INT32((int64_t)(i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)
这些宏存在以下问题:
- 他们正在对签名类型进行转换。有符号类型可能包含负值,然后右移的结果是 implementation-defined:它可以是算术或逻辑移位,具体取决于编译器。使用原始二进制文件时始终使用无符号类型。
- 将 32 位类型移动 32 位或更多位没有意义,因此出现警告。这在形式上是一个未定义的行为错误——C 标准说“如果右操作数的值为负数或大于或等于提升的左操作数的宽度,则行为未定义。”
- 宏像任何东西一样效率低下,因为它归结为
?:
检查的 8 个分支,对于某些在编译时可能是 pre-computed 的东西。没有必要打印这个 character-by-character. - 宏具有 non-existent 类型安全性,就像任何旧的 C 宏一样。
为了修复算法本身,8 位 look-up table 将是理想的,但意味着存储 256 个字符串。合理的速度与大小折衷通常是使用 4 位半字节 look-up 代替。示例:
static const char* BINSTR_NIBBLE [16] =
{
[0x00] = "0000", [0x01] = "0001", [0x02] = "0010", [0x03] = "0011",
[0x04] = "0100", [0x05] = "0101", [0x06] = "0110", [0x07] = "0111",
[0x08] = "1000", [0x09] = "1001", [0x0A] = "1010", [0x0B] = "1011",
[0x0C] = "1100", [0x0D] = "1101", [0x0E] = "1110", [0x0F] = "1111",
};
如果我们也想在它们之间打印一个 space,那么半字节可能是一个不错的选择,这是习惯。
可以使用 C 标准 _Generic
并通过强制转换为保证不是小整数类型的类型(有隐式提升为有符号的风险)来修复类型安全和 icky 签名类型。
解决上述所有问题的完整示例:
#include <stdio.h>
#include <stdint.h>
static const char* BINSTR_NIBBLE [16] =
{
[0x00] = "0000", [0x01] = "0001", [0x02] = "0010", [0x03] = "0011",
[0x04] = "0100", [0x05] = "0101", [0x06] = "0110", [0x07] = "0111",
[0x08] = "1000", [0x09] = "1001", [0x0A] = "1010", [0x0B] = "1011",
[0x0C] = "1100", [0x0D] = "1101", [0x0E] = "1110", [0x0F] = "1111",
};
#define print_bin(i) _Generic((i), \
int8_t: printf( "%s %s\n", \
BINSTR_NIBBLE[(uint32_t)(i) >> 4 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 0 & 0xFu] ), \
uint8_t: printf( "%s %s\n", \
BINSTR_NIBBLE[(uint32_t)(i) >> 4 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 0 & 0xFu] ), \
int16_t: printf( "%s %s %s %s\n", \
BINSTR_NIBBLE[(uint32_t)(i) >> 12 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 8 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 4 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 0 & 0xFu] ), \
uint16_t: printf( "%s %s %s %s\n", \
BINSTR_NIBBLE[(uint32_t)(i) >> 12 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 8 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 4 & 0xFu], \
BINSTR_NIBBLE[(uint32_t)(i) >> 0 & 0xFu] ) )
int main (void)
{
uint8_t u8 = 0xAB;
int16_t i16 = -1;
print_bin(u8);
print_bin(i16);
return 0;
}
输出
1010 1011
1111 1111 1111 1111
等等 - 扩展到 32 或 64 位类型的支持应该是微不足道的。这也可以进一步优化,但它已经比原来的宏快了好几倍,也更安全了。上面的 gcc x86 反汇编归结为 branch-free 字符串铲除(使用字符串池以节省内存):
.LC0:
.string "1011"
.LC1:
.string "1010"
.LC2:
.string "%s %s\n"
.LC3:
.string "1111"
.LC4:
.string "%s %s %s %s\n"
main:
sub rsp, 8
mov edx, OFFSET FLAT:.LC0
mov esi, OFFSET FLAT:.LC1
xor eax, eax
mov edi, OFFSET FLAT:.LC2
call printf
mov r8d, OFFSET FLAT:.LC3
mov edi, OFFSET FLAT:.LC4
xor eax, eax
mov rcx, r8
mov rdx, r8
mov rsi, r8
call printf
xor eax, eax
add rsp, 8
ret