67

我想使用一个期望这样的数据的函数:

void process(char *data_in, int data_len);

所以它实际上只是在处理一些字节。

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

我总是可以安全地将 a 传递给unsigned char *这个函数吗?

换句话说:

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

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

4

6 回答 6

104

如果您使用显式强制转换,简短的回答是肯定的,但要详细解释它,需要考虑三个方面:

1) 转换的合法性通常可以在 and 之间进行转换(对于某些类型),因为可以首先将源类型转换为(
这是标准转换,§4.10),然后可以使用转换为目标类型一个明确的(§5.2.9/13):signed T*unsigned T*Tvoid *void *static_cast

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 char并且unsigned char在形式上是三种不同的类型,§3.9.1/1。)

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

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

如果程序尝试通过非下列类型之一的泛左值访问对象的存储值,则行为未定义:

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

因此,此规则不允许通过 (or ) 访问( signed charor ) ,反之亦然 - 您应该能够毫无问题地执行此操作。charunsigned char*char

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

当从无符号变为有符号时,典型的效果是对于 0 到 128 之间的值没有任何反应,而 128 以上的值变为负数。反过来类似:当从有符号变为无符号时,负值将显示为大于 128 的值。

但标准实际上并不能保证这种行为。标准唯一保证的是,对于所有三种类型,charunsigned charsigned char所有位(不一定是 8,顺便说一句)都用于值表示。因此,如果您将一个解释为另一个,复制几份然后将其存储回原始位置,您可以确定不会丢失信息(根据您的要求),但您不一定知道值是什么实际上意味着(至少不是以完全可移植的方式)。

于 2013-03-02T08:11:05.593 回答
17

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

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

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

[正如一位编辑所指出的:普通char类型可以是有符号或无符号类型。C 和 C++ 标准明确允许两者之一(它始终是独立于任一unsigned charor的类型signed char,但与其中一个具有相同的范围)]

于 2013-02-25T23:39:16.507 回答
6

是的,您始终可以毫无问题地从 char 转换为 unsigned char,反之亦然。如果您运行以下代码,并将其与 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 开始,从 chars 到 unsigned chars 的转换效果很好,反之亦然,没有任何修改或丢失。

但是,从 i=128:255 开始,无法转换 chars 和 unsigned chars,否则您将有不同的输出,因为 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 编码(参考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);

该代码的输出将是:
红心 (♥)
方块 (♦)
梅花 (♣)
黑桃 (♠)

即使您将其每个字符都转换为无符号字符。

所以:

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

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

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

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

于 2013-03-06T18:24:28.087 回答
3

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

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

#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_unsigned里面的所有代码process都是相同的。唯一的区别是无符号和有符号。此示例显示黑盒中的代码确实受SIGN影响,并且在被调用者和调用者之间没有任何保证。

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

于 2013-03-05T06:13:22.790 回答
2

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

如果要在函数内部进行转换charunsigned char只需将char值分配给unsigned char变量或将char值强制转换为unsigned char.

如果您需要在不丢失数据的情况下转换unsigned charchar,这有点困难,但仍然可能:

#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 charchar返回值可以转换回与unsigned char参数相同的值的方式。

于 2013-02-25T23:53:36.417 回答
1

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

于 2013-03-02T09:06:16.043 回答