C/C++为什么要对二进制数据使用unsigned char?

nig*_*ils 51 c c++ bytebuffer character-encoding rawbytestring

是否真的有必要unsigned char像在一些处理字符编码或二进制缓冲区的库中一样使用二进制数据?要理解我的问题,请看下面的代码 -

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);
Run Code Online (Sandbox Code Playgroud)

两者printf's输出 correctly, where f0 a4 ad a2Unicode代码点的编码U+24B62 ()在十六进制中.

甚至memcpy还正确地复制了char所持有的位.

什么推理可能主张使用unsigned char而不是plain char

在其他相关问题unsigned char中突出显示,因为它是唯一的(字节/最小)数据类型,保证C规范没有填充.但正如上面的例子所示,输出似乎不受任何填充的影响.

我使用VC++ Express 2010和MinGW来编译上面的内容.虽然VC给出了警告

warning C4309: '=' : truncation of constant value

输出似乎没有反映出来.

PS这可以标记为可能重复的字节缓冲区应该是有符号的还是无符号的char缓冲区?但我的意图是不同的.我在问为什么一些似乎工作正常的东西char应该输入unsigned char

更新:引用N3337,

Section 3.9 Types

2对于普通可复制类型T的任何对象(基类子对象除外),无论对象是否包含类型T的有效值,组成对象的基础字节(1.7)都可以复制到char数组中或unsigned char.如果将char或unsigned char数组的内容复制回对象,则该对象应随后保持其原始值.

鉴于上述事实,我的原始示例是在char默认情况下的英特尔机器上signed char,我仍然不相信是否unsigned char应该优先考虑char.

还要别的吗?

Jen*_*edt 84

在C中,unsigned char数据类型是唯一同时具有以下三个属性的数据类型

  • 它没有填充位,所有存储位都有助于数据的值
  • 从该类型的值开始的无按位操作,当转换回该类型时,可能产生溢出,陷阱表示或未定义的行为
  • 它可以在不违反"别名规则"的情况下对其他数据类型进行别名,即通过不同类型的指针访问相同数据将保证看到所有修改

如果这些是您正在寻找的"二进制"数据类型的属性,那么您最终应该使用unsigned char.

对于第二个属性,我们需要一个类型unsigned.对于这些UCHAR_MAX+1,256在大多数99%的体系结构中,所有转换都使用模数arihmetic定义,此处为模数.所有较宽值的转换unsigned char因此仅对应于截断到最低有效字节.

另外两种字符类型通常不起作用.signed char无论如何,签名都是签名的,因此不适合转换不适合它的值.char不是固定为签名或未签名,但在您的代码移植到的特定平台上,即使它未经签名也可以签名.

  • 非常中立,坚持事实.+1 (11认同)

Tom*_*ner 13

在比较单个字节的内容时,您将获得大部分问题:

char c[5];
c[0] = 0xff;
/*blah blah*/
if (c[0] == 0xff)
{
    printf("good\n");
}
else
{
    printf("bad\n");
}
Run Code Online (Sandbox Code Playgroud)

可以打印"坏",因为,根据你的编译器,c [0]将符号扩展为-1,这与0xff没有任何相同之处


Lun*_*din 12

普通char类型是有问题的,不应该用于除字符串之外的任何东西.主要问题char是您无法知道它是有符号还是无符号:这是实现定义的行为.这char与其他不同int,int总是保证签名.

虽然VC给出了警告......截断常数值

它告诉您,您正在尝试将int文字存储在char变量中.这可能与签名有关:如果您尝试在签名字符内存储值> 0x7F的整数,则可能会发生意外情况.形式上,这是C中的未定义行为,但实际上如果尝试将结果打印为存储在(带符号)char中的整数值,则只会得到一个奇怪的输出.

在这种特定情况下,警告无关紧要.

编辑:

在其他相关问题中,unsigned char被突出显示,因为它是唯一的(字节/最小)数据类型,保证C规范没有填充.

理论上,根据C11 6.2.6.2,除了unsigned char和signed char之外的所有整数类型都允许包含"填充位":

"对于unsigned char以外的无符号整数类型,对象表示的位应分为两组:值位和填充位(不需要任何后者)."

"对于有符号整数类型,对象表示的位应分为三组:值位,填充位和符号位.不需要任何填充位; signed char不应有任何填充位."

C标准有意模糊和模糊,允许这些理论填充位,因为:

  • 它允许使用与标准8位符号表不同的符号表.
  • 它允许实现定义的签名和奇怪的有符号整数格式,例如一个补码或"符号和幅度".
  • 整数可能不一定使用分配的所有位.

但是,在C标准之外的现实世界中,以下内容适用:

  • 符号表几乎肯定是8位(UTF8或ASCII).存在一些奇怪的异常,但是在实现大于8位的符号表时,干净实现使用标准类型wchar_t.
  • 签名永远是两个补充.
  • 整数始终使用分配的所有位.

所以没有真正的理由使用unsigned char或signed char来躲避C标准中的一些理论场景.


Pao*_*oli 6

字节通常用作无符号8位宽整数.

现在,char没有指定整数的符号:在某些编译器上,char可以被签名,而在其他编译器上它可能是未签名的.

如果我向你编写的代码添加一个位移操作,那么我将有一个未定义的行为.添加的比较也会产生意外结果.

char c[5], d[5];
c[0] = 0xF0;
c[1] = 0xA4;
c[2] = 0xAD;
c[3] = 0xA2;
c[4] = '\0';
c[0] >>= 1; // If char is signed, will the 7th bit go to 0 or stay the same?

bool isBiggerThan0 = c[0] > 0; // FALSE if char is signed!

printf("%s\n", c);
memcpy(d, c, 5);
printf("%s\n", d);
Run Code Online (Sandbox Code Playgroud)

关于编译期间的警告:如果char已签名,那么您正在尝试分配值0xf0,该值不能在signed char(范围-128到+127)中表示,因此它将被转换为有符号值( - 16).

将char声明为signed将删除警告,并且在没有任何警告的情况下进行干净构建总是很好.