我可以将unsigned char转换为char,反之亦然?

我想使用一个需要像这样的数据的函数:

void process(char *data_in, int data_len); 

所以它只是处理一些字节。

但是当谈到原始字节的时候,我更喜欢使用“无符号字符”(它以某种方式“感觉”更正确的处理正值0到255的值),所以我的问题是:

我可以总是安全地传递一个unsigned char *到这个函数中吗?

换一种说法:

  • 是否可以保证能够安全地在char和unsigned char之间转换(转换),而不会丢失任何信息?
  • 我可以安全地转换(转换)指针之间的字符和无符号字符,没有任何信息的损失?

奖金:C和C ++的答案是否一样?

简单的答案是肯定的,如果你使用明确的演员,但详细解释,有三个方面来看:

1)转换的合法性
在任何方向上对带unsigned T* signed T*unsigned T* (对于某些typesT )进行转换通常是可能的,因为源types可以首先转换为void * (这是一个标准转换,§4.10),并且可以转换void *到一个明确的static_cast (§5.2.9/ 13)的目标types:

 static_cast<unsigned char*>(static_cast<void *>(data_in)) 

这可以缩写为(§5.2.10/ 7)

 reinterpret_cast<unsigned char *>(data_in) 

因为char是标准布局types(§3.9.1/ 7,8和§3.9/ 9),并且签名不会改变alignment(§3.9.1/ 1)。 它也可以写成C风格的演员:

 (unsigned char *)(data_in) 

同样,这unsigned*到有signed*和双向都有效。 还有一个保证,如果你以这种方式应用这个过程,那么指针值(即它指向的地址)将不会改变(§5.2.10/ 7)。

所有这些不仅适用于signed char *unsigned char *之间的转换,还适用于char * / unsigned char *char * / signed char * 。 ( charsigned charunsigned char在forms上是三种不同的types,§3.9.1/ 1)。

要清楚的是,你使用三种铸造方法中的哪一种并不重要,但是你必须使用一种。 仅仅传递指针将不起作用,因为转换是合法的,不是标准转换,所以不会隐式执行(如果您尝试,编译器会发出错误)。

2)明确的价值观念
如果在函数内部取消引用指针,即执行*data_in来检索底层字符的glvalue,会发生什么? 这是明确的和合法的吗? 这里的相关规则是严格别名规则(§3.10/ 10):

如果程序试图通过以下types之一的glvalue来访问对象的存储值,则行为是未定义的:

  • […]
  • types是与对象的dynamictypes对应的有符号或无符号types,
  • […]
  • 一个charunsigned chartypes。

因此,通过unsigned char* (或char )访问已signed char (或char ),反之亦然,这个规则是不允许的 – 你应该可以做到这一点没有问题。

3)结果值
在取消types转换的指针后,你能使用你得到的值吗? 需要记住的是,上述指针的转换和取消引用相当于重新解释(而不是改变!)存储在字符地址处的位模式。 那么当一个有符号字符的位模式被解释为一个无符号字符时(反之亦然),会发生什么?

从无符号到有符号时, 典型的影响是0到128之间没有任何变化,而128以上的值变成负值。 反过来也是如此:当从有符号到无符号时,负值将显示为大于128的值。

但是这个行为实际上并没有被标准保证 。 标准唯一保证的是对于所有三种types, charunsigned charsigned char ,所有位(不一定是8,btw)用于值表示。 因此,如果您将其中一个解释为另一个,请复制一份,然后将其存储回原始位置,则可以确保不会有任何信息丢失(如您所要求的那样),但您不一定会知道这些值实际上意味着(至less不是以完全便携的方式)。

unsigned char或有signed char只是解释:没有转换发生。

由于您正在处理字节,为了显示意图,最好声明为

 void process(unsigned char *data_in, int data_len); 

[正如一位编辑指出的:一个普通的char可能是有符号或无符号types。 C和C ++标准明确允许(它总是与unsigned charsigned char分开的types,但与其中之一具有相同的范围)]

您可以将指针传递给其他types的char ,但您可能需要明确地将其转换。 指针保证是相同的大小和相同的值。 转换过程中不会有任何信息丢失。

如果要在函数内将char转换为unsigned char ,只需将一个char值赋给unsigned charvariables,或将char值赋给unsigned char

如果您需要将unsigned char转换为char而不丢失数据,则有点困难,但仍有可能:

 #include <limits.h> char uc2c(unsigned char c) { #if CHAR_MIN == 0 // char is unsigned return c; #else // char is signed if (c <= CHAR_MAX) return c; else // ASSUMPTION 1: int is larger than char // ASSUMPTION 2: integers are 2's complement return c - CHAR_MAX - 1 - CHAR_MAX - 1; #endif } 

这个函数将把unsigned char转换为char ,使得返回的值可以被转换回与参数相同的unsigned char值。

您确实需要查看要process()的代码process()以了解是否可以安全地传入未签名的字符。 如果函数使用字符作为数组中的索引,则不可以使用无符号数据。

在语义上, unsigned char *char *之间的传递是安全的,即使在它们之间进行转换,就像在c ++中一样。

但是,请考虑以下示例代码:

 #include "stdio.h" void process_unsigned(unsigned char *data_in, int data_len) { int i=data_len; unsigned short product=1; for(; i--; product*=data_in[i]) ; for(i=sizeof(product); i--; ) { data_in[i]=((unsigned char *)&product)[i]; printf("%d\r\n", data_in[i]); } } void process(char *data_in, int data_len) { int i=data_len; unsigned short product=1; for(; i--; product*=data_in[i]) ; for(i=sizeof(product); i--; ) { data_in[i]=((unsigned char *)&product)[i]; printf("%d\r\n", data_in[i]); } } void main() { unsigned char a[]={1, -1}, b[]={1, -1}; process_unsigned(a, sizeof(a)); process(b, sizeof(b)); getch(); } 

输出:

 0
 255
 -1
 -1

process_unsignedprocess内的所有代码都是相同的。 唯一的区别是没有签名和签名。 这个示例显示黑盒子里的代码受到SIGN的影响,被调用者和调用者之间什么也没有保证。

因此,我会说,它只适用于通过 ,但没有任何其他的可能性得到保证。

是的,你总是可以从char转换为unsigned char, 反之亦然没有问题。 如果您运行以下代码,并将其与ASCII表(参考http://www.asciitable.com/ )进行比较,您可以亲自看到一个certificate,以及C / C ++如何处理转换 – 它们处理完全相同的方式:

 #include "stdio.h" int main(void) { //converting from char to unsigned char char c = 0; printf("%d byte(s)\n", sizeof(char)); // result: 1byte, ie 8bits, so there are 2^8=256 values that a char can store. for (int i=0; i<256; i++){ printf("int value: %d - from: %c\tto: %c\n", c, c, (unsigned char) c); c++; } //converting from unsigned char to char unsigned char uc = 0; printf("\n%d byte(s)\n", sizeof(unsigned char)); for (int i=0; i<256; i++){ printf("int value: %d - from: %c\tto: %c\n", uc, uc, (char) uc); uc++; } } 

我不会发布输出,因为它有太多的线! 在输出中可以注意到,在每个部分的前半部分,即从i = 0:127,从字符到无符号字符的转换, 反之亦然 ,没有任何修改或丢失。

然而,从i = 128:255,字符和无符号的字符不能被铸造,或者你会有不同的输出,因为无符号的字符保存值[0:256]和字符保存值间隔[-128:127 ])。 尽pipe如此,下半部分的行为并不重要,因为在C / C ++中,一般情况下,只能使用字符/无符号字符作为ASCII字符,它们只能取128个不同的值,其他的128个值对于无符号的字符)从不使用。

如果你从不把一个值放在一个不代表一个字符的char中,而且你永远不会把一个值放在一个不代表一个字符的unsigned char中,那么一切都会好的!

额外的:即使你用C / C ++在你的string中使用UTF-8或其他编码(对于特殊字符),所有这种types的强制转换都是可以的,例如使用UTF-8编码(参考http:// lwp.interglacial.com/appf_01.htm ):

 char hearts[] = {0xe2, 0x99, 0xa5, 0x00}; char diamonds[] = {0xe2, 0x99, 0xa6, 0x00}; char clubs[] = {0xe2, 0x99, 0xa3, 0x00}; char spades[] = {0xe2, 0x99, 0xa0, 0x00}; printf("hearts (%s)\ndiamonds (%s)\nclubs (%s)\nspades (%s)\n\n", hearts, diamonds, clubs, spades); 

该代码的输出将是:
心(♥)
钻石(♦)
俱乐部(♣)
黑桃(♠)

即使你把每个字符都转换成无符号的字符。

所以:

  • “我可以总是安全地通过一个无符号的char *进入这个函数吗?” 是!

  • “是否保证我可以安全地在char和unsigned char之间转换(转换),而不会丢失任何信息? 是!

  • “我可以安全地转换(转换)指向char和unsigned char的指针,而不会丢失任何信息? 是!

  • “C和C ++中的答案是一样的吗?” 是!