“由于我想使用文件中的原始位模式,我怎样才能将我的有符号字符转换/转换为无符号字符,以便位模式保持不变?”
int
正如有人在之前对同一主题的问题的回答中已经解释的那样,任何小整数类型,无论是有符号的还是无符号的,只要在表达式中使用,都会被提升为该类型。
C11 6.3.1.1
“如果 int 可以表示原始类型的所有值(受宽度限制,对于位域),则该值将转换为 int;否则,将其转换为无符号 int。这些称为整数提升。”
此外,正如在同一个答案中所解释的,整数文字始终是 type int
。
因此,您的表达式将归结为伪代码(int) & (int) & (int)
。这些操作将在三个临时 int 变量上执行,结果将是 int 类型。
现在,如果原始数据包含可能被解释为特定符号表示的符号位的位(实际上这将是所有系统的二进制补码),您将遇到问题。因为这些位将在从signed char 提升到int 时保留。
然后按位 & 运算符对每个位执行 AND,无论其整数操作数 (C11 6.5.10/3) 的内容如何,无论是否有符号。如果您在原始签名字符的签名位中有数据,那么它现在将丢失。因为整数文字(0xC0 或 0x80)将没有设置对应于符号位的位。
解决方案是防止符号位被传输到“临时 int”。一种解决方案是将 c[i] 强制转换为 unsigned char,这是完全明确的 (C11 6.3.1.3)。这将告诉编译器“这个变量的全部内容是一个整数,没有需要关注的符号位”。
更好的是,养成在各种形式的位操作中始终使用无符号数据的习惯。重写表达式的纯粹、100% 安全、符合 MISRA-C 的方法是:
if ( ((uint8_t)c[i] & 0xc0u) & 0x80u) > 0u)
u 后缀实际上强制表达式为 unsigned int,但最好始终强制转换为预期类型。它告诉代码的读者“我实际上知道我在做什么,而且我也理解 C 中所有奇怪的隐式提升规则”。
然后,如果我们知道我们的十六进制,(0xc0 & 0x80)
那是没有意义的,它总是正确的。并且x & 0xC0 & 0x80
始终与 相同x & 0x80
。因此将表达式简化为:
if ( ((uint8_t)c[i] & 0x80u) > 0u)
“在任何地方都有这些“实现定义的方面”的列表吗?
是的,C 标准在附录 J.3 中方便地列出了它们。但是,在这种情况下,您遇到的唯一实现定义的方面是整数的符号实现。在实践中,这始终是二进制补码。
编辑:问题中引用的文本与各种按位运算符将产生实现定义的结果有关。即使在没有确切参考的附录中,这也只是简单地提到了实现定义。实际的第 6.5 章对 & | 的 impl.defined 行为没有多说。等等。唯一明确提到它的运算符是 << 和 >>,其中左移负数甚至是未定义的行为,但右移是实现定义的。