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

时间:2013-02-25 23:37:15

标签: c++ c

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

void process(char *data_in, int data_len);

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

但是当谈到原始字节时,我更习惯使用“unsigned char”(它在某种程度上“感觉”更适合处理正0到255的值),所以我的问题是:

我可以随时安全地将unsigned char *传递给此功能吗?

换句话说:

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

奖励:C和C ++的答案是否相同?

6 个答案:

答案 0 :(得分:80)

如果使用明确的演员表,简短的回答是肯定的,但要详细解释,有三个方面需要注意:

1)转换的合法性
通常可以在任一方向上转换signed T*unsigned T*(对于某种类型T),因为源类型可以先转换为void *(这是标准转换, §4.10),void *可以使用显式static_cast(§5.2.9/ 13)转换为目标类型:

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

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

reinterpret_cast<unsigned char *>(data_in)

因为char是标准布局类型(§3.9.1/ 7,8和§3.9/ 9),并且签名不会改变对齐(§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正式分为三种不同类型,§3.9.1/ 1。)

要清楚,使用三种演员方法中的哪一种并不重要,但必须使用一种。仅仅传递指针是行不通的,因为转换虽然合法,但不是标准转换,因此不会隐式执行(如果你尝试,编译器会发出错误)。

2)对价值观的明确定义
如果在函数内部取消引用指针,即执行*data_in以检索基础字符的glvalue,会发生什么情况;这个定义明确且合法吗?这里的相关规则是严格别名规则(§3.10/ 10):

  

如果某个程序试图通过以下某种类型之外的 glvalue 访问对象的存储值,则行为未定义:

     
      
  • [...]
  •   
  • 与对象的动态类型对应的有符号或无符号类型的类型
  •   
  • [...]
  •   
  • charunsigned char类型。
  •   

因此,此规则不允许通过signed char(或char)访问unsigned char*(或char)(或char),反之亦然 - 您应该能够这样做没有问题。

3)结果值
在对类型转换指针进行反省略后,您是否能够使用您获得的值?重要的是要记住,上述指针的转换和解除引用相当于重新解释(不改变!)存储在字符地址处的位模式。那么当有符号字符的位模式被解释为无符号字符的位模式时会发生什么呢?(反之亦然)?

当从无符号转为有符号时,典型效果将是0到128之间没有任何反应的值,而大于128的值将变为负数。反之亦然:从有符号转为无符号时,负值将显示为大于128的值。

但标准实际上并未保证此行为 。标准保证的唯一内容是对于所有三种类型unsigned charsigned char和{{1}},所有位(不一定是8,btw)都用于值表示。因此,如果您将其中一个解释为另一个,制作一些副本然后将其存储回原始位置,您可以确定不会丢失任何信息(如您所示),但您不一定知道这些值是什么实际上是指(至少不是以完全可移植的方式)。

答案 1 :(得分:15)

unsigned charsigned char只是解释:没有转化。

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

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

[正如编辑所说:普通char可以是签名或无签名类型。 C和C ++标准明确允许(它始终是unsigned charsigned char的单独类型,但与其中一个具有相同的范围)]

答案 2 :(得分:2)

您可以将指针传递给其他类型的char,但您可能需要显式转换它。指针保证大小相同,值相同。转换过程中不会有任何信息丢失。

如果您想在函数内将char转换为unsigned char,只需将char值分配给unsigned char变量或投射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值。

答案 3 :(得分:1)

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

答案 4 :(得分:1)

从语义上讲,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中的所有代码都只是 IDENTICAL 。唯一的区别是未签名和签名。此示例显示黑框中的代码确实受 SIGN 的影响,并且在被调用方和调用方之间保证

因此,我会说,它仅适用于传递,但不保证任何其他可能性。

答案 5 :(得分:1)

是的,您始终可以从char转换为unsigned char&amp; 反之亦然没有问题。如果您运行以下代码,并将其与ASCII表(参考http://www.asciitable.com/)进行比较,您可以自己查看证明,以及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, i.e. 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开始,字符和无符号字符不能被输出,或者你会有不同的输出,因为unsigned char保存了[0:256]中的值而char保存了区间中的值[ - 128:127])。然而,这个下半部分的行为是无关紧要的,因为在C / C ++中,一般来说,你只能用chars / unsigned chars作为ASCII字符,它只能带128个不同的值,其他128个值(chars为负数或为负数)对于未签名的字符,从未使用过。

如果你从未在不代表字符的char中加入值,并且你从未在不代表字符的unsigned char中放置值,那么一切都会好的!

额外:即使您在使用C / C ++的字符串中使用UTF-8或其他编码(对于特殊字符),使用这种类型转换的所有内容都可以,例如,使用UTF-8编码(参考。{ {3}}):

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);

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

即使你将每个字符转换为无符号字符。

这样:

  • “我可以安全地将unsigned char *传递给此函数吗?” 是的!

  • “是否可以保证我可以随意在char和unsigned char之间转换(强制转换),而不会丢失任何信息?” 是的!

  • “我可以随意转换(强制转换)指向char和unsigned char的指针,而不会丢失任何信息吗?” 是的!

  • “在C和C ++中答案是否相同?” 是的!