C中将非Ascii字符转为int,多余的位补上1而不是0

Converting non-Ascii characters to int in C, the extra bits are supplemented by 1 rather than 0

在用C写代码的时候无意中发现,对于非Ascii字符,从char(1字节)转换为int(4字节)后,多出的位(3 bytes) 补1而不是补0。(至于Ascii字符,多余的位补0。)例如:

char c[] = "ā";
int i = c[0];
printf("%x\n", i);

结果是 ffffffc4,而不是 c4 本身。 (ā 的 UTF-8 编码是 \xc4\x81。)

另一个相关的问题是,当对非Ascii字符进行右移操作时>>,左端的额外位也会被补上1而不是0,即使char变量是显式的转换为unsigned int(至于signed int,多出的bit在我的OS中补1)。例如:

char c[] = "ā";
unsigned int u_c;
int i = c[0];
unsigned int u_i = c[0];

c[0] = (unsigned int)c[0] >> 1; 
u_c = (unsigned int)c[0] >> 1;      
i = i >> 1;
u_i = u_i >> 1;
printf("c=%x\n", (unsigned int)c[0]); // result: ffffffe2. The same with the signed int i.
printf("u_c=%x\n", u_c); // result: 7fffffe2.
printf("i=%x\n", i); // result: ffffffe2.
printf("u_i=%x\n", u_i); // result: 7fffffe2. 

现在我对这些结果感到困惑...它们是与 char、int 和 unsigned int 的数据结构有关,还是与我的操作系统 (ubuntu 14.04) 相关,或与 ANSI 相关C要求?我试过用 gcc(4.8.4) 和 clang(3.4) 编译这个程序,但没有区别。

非常感谢!

实现定义 char 是有符号还是无符号。在 x86 计算机上,char 通常是 有符号整数类型 ;在 ARM 上,它通常是 无符号整数类型 .

有符号整数在转换为更大的有符号类型时将符号扩展

有符号整数转换为无符号整数将使用modulo算法将有符号值包装到范围内无符号类型的无符号类型的重复加减无符号类型的最大值+1.


解决方案是 use/cast 到 unsigned char 如果您希望值 可移植 零扩展,或者存储范围 0..255.

同样,如果要在 -127..127/128 范围内存储有符号整数,请使用 signed char.

如果签名无关紧要,请使用 char - 实现可能会选择对平台最有效的类型。


同样,对于作业

unsigned int u_c; u_c = (uint8_t)c[0];,

由于-0x3c-60不在uint16_t的范围内,那么实际值为(modUINT16_MAX + 1) 落在 uint16_t 范围内; iow,我们添加或减去 UINT16_MAX + 1 请注意,整数提升可能会在这里欺骗,因此如果在 C 代码中,您可能需要强制转换 )直到值在范围内。 UINT16_MAX 自然总是 0xFFFFF;将其加 1 得到 0x100000x10000 - 0x3C就是你看到的0xFFC4。然后 uint16_t 值零扩展到 uint32_t 值。

如果你在 charunsigned 的平台上 运行,结果会是 0xC4!


顺便说一句,在i = i >> 1;中,i是一个带符号的整数,具有负值; C11 表示值为 implementation-defined, so the actual behaviour can change from compiler to compiler. The GCC manuals state

Signed >> acts on negative numbers by sign extension.

然而,一个严格符合的程序不应该依赖于此。