在C++中 char 类型用来表示字符,其数据宽度为一个字节,通常也可以将 char 用作占一个字节的整数。但整数类型又分为无符号整数和有符号整数,对于具有相同数据宽度的有符号整数和无符号整数,它们的允许的取值范围是不同的。在将 char 当作整型数据使用时,要格外注意其符号特性。

char 类型的符号特性

最近在移植一个数据压缩算法时遇到这样的问题:原始代码是在x86架构的Windows操作系统上运行的,移植目标是ARM架构的Linux操作系统,在移植后发现某个解压缩算法的处理结果不正确。

经过一番调试,最终将问题定位在 char 类型的符号特性上。原算法在Windows系统的MSVC编译环境中使用了 char 作为单字节有符号整数,而在ARM架构的Linux系统上使用gcc编译时 char 却是无符号的。

char 类型的符号特性在不同的编译器下是不同的。

曾经我也想当然地认为char是有符号的,即像整型变量一样 signed 关键字是被省略掉了。但实际上,在C++标准中, char 类型是作为字符类型与整数类型分开描述的。

The type char , signed char and unsigned char are collectively called the character types. The implementation shall define char to have the same range, representation, and behavior as either signed char or unsigned char .

CHAR_MIN , defined in <limits.h> , will have one of the values 0 or SCHAR_MIN , and this can be used to distinguish the two options. Irrespective of the choice made, char is separate type from the other two and is not compatible with either.

以上摘自C11标准(n1587),可见标准中并未规定 char 类型的符号特性,即 char 可以是 signedunsigned ,并由具体的实现(implementation)决定。某些处理器和指令集或应用程序二进制接口更喜欢使用有符号的字符类型,这样能够完美地映射一些机器码指令,反之亦然。

使用 uint8_t 表示单字节无符号整数

通常,当我们把 char 作为字符类型使用时并不需要考虑它的符号特性,因为字符类型变量存储的ASCII码在显示时会转化为英文字母。这时, char 是有符号的或无符号的并不影响它所存储的ASCII码。

由于 char 类型只占一个字节(8bit),并且又是基本类型,用它来表示单字节整数是非常方便的。但是,不确定的符号特性又使它不适合作为一种整数类型。更推荐使用 uint8_t 表示无符号单字节整数, int8_t 表示有符号单字节整数,这种写法代码可读性更好,也更利于移植。

相关的编译器选项

gcc 提供了 -fsigned-char-funsigned-char 编译选项,用于指定 char 类型的默认符号特性。但是大多数情况下并不推荐使用使用这两个参数。因为修改后可能在极端情况下破坏调用约定(call conversion)和应用程序二进制接口(ABI, application binary interface)。

msvc 提供了 /J 参数,用于将默认的 char 类型从 signed char 变更为 unsigned char ,并在 char 拓展到更宽数据类型时(如 int ),使用无符号整数的拓展方式。如果在ATL/MFC工程中使用了这个参数,开发环境可能会报错;尽管可以通过定义 _ATL_ALLOW_CHAR_UNSIGNED 禁用这个错误,但这样做可能并不奏效。

综上,尽管编译器都提供了参数用于更改默认的 char 类型的符号特性,但它们都会在某些情况下失效。所以,如果真的需要单字节的整型变量,还是明确地声明为 signed charunsigned char 吧,或者使用标准库提供的 int8_tuint8_t