何时使用无符号字符指针

11

unsigned char指针有什么用?我在许多地方看到将指针强制转换为unsigned char指针的情况。我们为什么这样做?

我们收到一个指向int的指针,然后将其类型转换为unsigned char*。但是,如果我们尝试使用cout打印该数组中的元素,则不会打印任何内容。为什么?我不理解。我是C++新手。

编辑下面是示例代码

int Stash::add(void* element)
{
    if(next >= quantity)
    // Enough space left?
        inflate(increment);
    
    // Copy element into storage, starting at next empty space:
    int startBytes = next * size; 
    unsigned char* e = (unsigned char*)element;
    for(int i = 0; i < size; i++)
        storage[startBytes + i] = e[i];
    next++;
    return(next - 1); // Index number
}

当转换为字符指针时,第一个字节可能为零,这与字符串终止符相同,因此不会打印任何内容。如果您能展示您实际执行的操作,即发布一些代码,那将更有帮助。请创建一个SSCCE并添加到问题中。 - Some programmer dude
但我认为如果第一个字节为零,那么信息将会丢失,实际上我正在尝试打印所有四个字节,但是它没有打印任何东西。 - Ankit_ceo2
2
你的问题似乎更多关于“为什么”而不是“何时”。很多时候,unsigned char *被用作字节级访问方法,以便访问变量或内存地址中的更正式类型。它有许多好处,其中包括免疫严格别名规则和标准保证对任何地址都具有对齐性。如果你对C语言比较熟悉,那么新学习C++应该不会太难。如果你是编程新手,我认为这可能是一个理解上的挑战。也许你有一些代码和想法需要提出问题? - WhozCraig
我把你的代码移到了你的问题中。关于它的任何评论,你可以在问题中发表,或者对其他评论的回复,可以在这里发布。 - WhozCraig
4个回答

11
你实际上正在寻找指针算术
unsigned char* bytes = (unsigned char*)ptr;
for(int i = 0; i < size; i++)
    // work with bytes[i]

在这个例子中,bytes[i] 等于 *(bytes + i),它被用来访问地址为:bytes + (i* sizeof(*bytes)) 的内存。换句话说:如果你有一个 int* intPtr 并且你尝试访问 intPtr[1],实际上你正在访问存储在 bytes 中的整数:4 到 7。
0  1  2  3
4  5  6  7 <-- 

你指针所指向的类型大小会影响它在递增/递减后指向的位置。因此,如果你想按字节迭代数据,你需要一个指向1个字节大小类型的指针(这就是为什么要使用unsigned char*)。

unsigned char通常用于保存二进制数据,其中0是有效值且仍然是您的数据的一部分。当使用“裸”unsigned char*时,您可能需要保存缓冲区的长度。

char通常用于保存表示字符串的字符,0等于'\0'(终止字符)。如果您的字符缓冲区始终以'\0'结尾,则不需要知道其长度,因为终止字符恰好指定了数据的结尾。

请注意,在这两种情况下,最好使用某个对象来隐藏数据的内部表示,并将为您处理内存管理(请参见RAII idiom)。因此,更好的想法是使用std::vector<unsigned char>(用于二进制数据)或std::string(用于字符串)。


有时我看到他们将 unsigned char* 与另一个 sizeof struct 结合起来,像这样:return (unsigned char *)data + sizeof(Header); (其中 data 是 void 指针),这是为了用头的大小计算 void 指针的长度吗? - TomSawyer

8
在C语言中,unsigned char是唯一保证没有陷阱值的类型,并且保证复制会得到完全一致的比特位图像。因此,它通常用于“原始内存”(例如,memcpy的语义是基于unsigned char定义的)。在此基础上,C++还将这种保证扩展到char类型。
另外,无符号整型类型通常用于执行位运算(如&|>>等)。unsigned char是最小的无符号整型类型,在操作需要使用位运算的小值数组时可以使用它。有时也会使用它来获得溢出时的模数行为,尽管这在更大的类型(例如计算哈希值时)中更为频繁。这两个原因都适用于无符号类型;unsigned char通常只有在需要减少内存使用时才会使用它们。

1
“C++也将这种保证扩展到了char。”——我们能提供一个出处吗? - emlai
@emlai 这是不言自明/易于证明的。https://dev59.com/kmAg5IYBdhLWcg3wDHQ3#24052128 如果你愿意,你可以破坏内存并迭代所有2^8个可能的值(由于sizeof(char)保证全面),并亲自证明它。 - JamesTheAwesomeDude

4

unsigned char类型通常用作单个二进制数据字节的表示,因此数组通常用作二进制数据缓冲区,其中每个元素都是单个字节。

unsigned char*构造将是指向二进制数据缓冲区(或其第一个元素)的指针。

我不确定标准确切地说了unsigned char的大小,是否固定为8位。 通常是这样的。 我会尝试找到并发布它。

看了你的代码之后

当您使用像void* input这样的东西作为函数的参数时,您故意剥夺了有关输入原始类型的信息。 这非常强烈地表明,输入将以非常普遍的方式进行处理。 也就是说,作为任意字节字符串。 另一方面,int* input 则暗示它将被视为一组带符号整数的“字符串”。

void*主要用于输入被编码或以比特/字节方式处理的情况,因为您无法对其内容进行结论。

然后在函数中,您似乎想将输入作为一系列字节来处理。 但是要对对象进行操作,例如执行operator=(赋值),编译器需要知道要做什么。 由于您将输入声明为void*,因此如*input = something这样的赋值将没有意义,因为*inputvoid类型。 为了使编译器将input元素视为“最小的原始内存单元”,您需要将其转换为适当的类型,即unsigned int

cout可能无法正常工作,因为类型转换错误或者不符合预期。 char*被认为是一个以空字符结尾的字符串,并且很容易混淆代码中的singedunsigned版本。如果将unsinged char*传递给ostream::operator<<作为char*,它将将byte输入视为普通ASCII字符,其中0表示字符串结束而不是整数值0。 当您想要打印内存内容时,最好明确进行指针转换。

还请注意,要打印缓冲区的内存内容,您需要使用循环,否则打印函数将不知道何时停止。


1
C和C++定义字符类型(charunsigned charsigned char)的大小为1个字节,并要求它们至少有8位。曾经有一种具有9位char的机器,现在可能已经不存在了,还有一些具有32位char的机器。(当然,从历史上看,有很多字节少于8位的机器,但是C不允许这样做。) - James Kanze
@James,谢谢。我提到这个是因为我记得并不保证它始终是8位。我想要明确一点,以防有人要实现一些低级网络协议或将二进制文件从一个系统移动到另一个系统时,可能会遇到这样的注意事项。 - luk32
1
很多事情取决于你需要多么便携。对于大多数人来说,可移植性约束条件将足够宽松,以允许假设char为8位,但确实存在一些机器不是这样的。 - James Kanze

0
无符号字符指针在需要逐字节访问数据时非常有用。例如,一个从一个区域复制数据到另一个区域的函数可能需要这样做:
void memcpy (unsigned char* dest, unsigned char* source, unsigned count)
{
    for (unsigned i = 0; i < count; i++)
        dest[i] = source[i];
}

这也与字节是内存中可寻址的最小单元有关。如果您想从内存中读取比一个字节更小的任何内容,则需要获取包含该信息的字节,然后使用位操作选择信息。

您可以使用 int 指针很好地复制上述函数中的数据,但在某些情况下,这可能会复制 4 字节的块,这可能不是正确的行为。

为什么在尝试使用 cout 时屏幕上没有任何显示,最可能的解释是数据以零字符开头,在 C++ 中它标志着一串字符的结尾。


如果以0字符开头,仍应打印其他3个字符的值。如果在代码中的for循环中不打印任何内容,则为:for(int i = 0; i < size; i++) cout<< e[i]; storage[startBytes + i] = e[i]; 如果我将其更改为cout<< (int)e[i];则在上面的代码中打印第一个迭代的值,然后打印3个垃圾值。 - Ankit_ceo2
1
你完全可以使用int指针复制上面函数中的数据。不,你完全不能!除了unsigned char(我认为特别是有符号类型)之外的类型,不能保证(A)覆盖底层内存的所有位或(B)允许捕获/无效值,这可能导致将任意字节重新解释为int。在此处使用除unsigned char *以外的任何指针都是固有的,并且非常不可移植。实现可能将其用作平台相关细节,但用户不应该这样做。 - underscore_d

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接