内存的逐字节读取: "signed char *" 与 "unsigned char *"

20

经常需要按字节一次从内存中读取,就像这个朴素的memcpy()实现:

void *memcpy(void *dest, const void *src, size_t n)
{
    char *from = (char *)src;
    char *to   = (char *)dest;

    while(n--) *to++ = *from++;

    return dest;
}

然而,有时候我看到人们明确地使用 unsigned char * 而不是仅仅使用 char *

当然,charunsigned char 可能并不相等。但是如果按字节读写内存时,我使用 char *signed char * 或者 unsigned char * 是否会有区别呢?

更新:实际上,我充分了解 c=200 可能根据 c 的类型而有不同的值。我在这里所问的是,为什么有时人们在读取内存时使用 unsigned char * 而不仅仅是使用 char *,例如将 uint32_t 存储在 char[4] 中。


但这有何区别呢?对什么有所影响呢? - Mitch Wheat
Mitch:好的,问题已经解决了。 - Philip
无符号字符更清晰地表达了处理原始字节而非字符的概念,即使二进制值相同也是如此。 - nos
6个回答

23
你应该使用 unsigned char。C99标准规定只有 unsigned char 类型是保证紧密排列(没有填充位)的,同时定义了你可以通过将任何对象(除了位域)复制到一个 unsigned char 数组中来精确复制任何对象的 对象表示 以字节为单位。

我的合理解释是,如果您使用指针按字节访问对象,则应使用 unsigned char

参考: http://blackshell.com/~msmud/cstd.html#6.2.6.1 (来自 C99)


14

这是C++与C不同之处之一。一般来说,C只保证对于unsigned char的原始内存访问有效;而char可能签名,在1的补码或有符号数表示机器上,-0可能会自动转换为+0,从而改变位模式。由于某种未知的原因,C++委员会将支持透明复制(位模式不变)的保证扩展到了charunsigned char上;在1的补码或有符号数表示机器上,实现者别无选择,只能将纯粹的char设置为unsigned,以避免这样的副作用。(当然,今天大多数程序员并不关心这样的机器。)

总之,结果是,老一辈的程序员,他们来自C语言背景(也许曾经在1的补码或有符号数表示机器上工作过),会自动使用unsigned char。另外一个惯例是,将普通的char仅用于唯一的字符数据,将signed char用于非常小的整数值,将unsigned char用于原始内存,或者用于位操作时。这样的规则可以使读者区分不同的用途(只要严格遵守规则即可)。


7
我认为你每次在上面提到“2的补码”,实际上指的是“1的补码”。但对于C语言中的2的补码有符号类型,构成由符号位为1和其他所有位为0的值是否是陷阱值也是实现定义的(如果不是,则当然是该类型的最小值)。因此,即使是一些使用2的补码的硬件,在将char复制时如果char被标记为有符号位,则会失败。 - Steve Jessop
我将会把 Steve Jessop 的值翻倍。现在我们都使用二进制补码机器。 - Ulterior
@SteveJessop 是的。它最初是一个打字错误,然后被复制了。(但我从未见过一个在最大负值上陷入困境的2的补码机器。虽然这会让生活变得更容易:-INT_MIN不是int的合法值的事实意味着你必须在转换例程中非常注意。) - James Kanze
我想知道C语言允许陷阱值的动机是因为有人提出了一个例子,还是有人威胁要构建一个,或者只是一厢情愿的想法。 - Steve Jessop
@SteveJessop 或者只是不确定性。当时1的补码机器的存在是广为人知的。它们在处理-0方面的实际行为不太清楚,可能没有人能确定这样的情况不存在,并且没有人觉得禁止它有任何优势。 - James Kanze

2

在您的代码示例中并没有什么区别。但是,如果您想要显示/打印字节的值,则会有所不同(因为最高位的解释方式不同),此时使用 unsigned char 似乎更合适。


0
如果您想按字节读写内存,请考虑使用 std::byte 而非 unsigned char

https://en.cppreference.com/w/cpp/types/byte

这种类型允许进行按位逻辑操作,并且可以帮助避免难以调试的编程错误。


0

这取决于您想在 char 中存储什么。

有符号的 char 可以给您提供从 -127 到 127 的范围,而无符号的 char 范围从 0 到 255。

对于指针算术运算来说,这并不重要。


0
#include<stdio.h>
#include<string.h>

int main()
{

unsigned char a[4]={254,254,254,'\0'};
unsigned char b[4];
char c[4];

memset(b,0,4);
memset(c,0,4);

memcpy(b,a,4);
memcpy(c,a,4);
int i;
for(i=0;i<4;i++)
{
    printf("\noriginal is %d",a[i]);
    printf("\nchar %d is %d",i,c[i]);
    printf("\nunsigned char %d is %d \n\n",i,b[i]);
}

}

输出为

original is 254
char 0 is -2           
unsigned char 0 is 254 


original is 254
char 1 is -2
unsigned char 1 is 254 


original is 254
char 2 is -2
unsigned char 2 is 254 


original is 0
char 3 is 0
unsigned char 3 is 0 

所以在这种情况下,char和unsign具有相同的值,因此没有关系。

编辑

如果您仍将任何内容读取为signed char,则在该情况下,最高位也将被复制,因此没有关系。


2
我不会对同行的答案进行负评,但“仅凭一个工作示例证明”并不适用于C语言,因为我们只是在谈论一种实现方式,甚至更糟糕的是,可能只是其中的一个版本。 - u0b34a0f6ae
@kaizer.se 顺便说一下,如果你因为这是一个同行的答案而不给差评,那会更值得怀疑。但我也不想鼓励你给这个答案点踩,只是一般性的评论。 - Christian Rau
@Christian:我不理解你的观点。一般来说,我认为在Stack上的反对票太少了,只有很少的人这么做,如果我有一次弃权,也不会发生什么奇怪的事情。 - u0b34a0f6ae
2
@kaizer.se 我并没有说你应该这么做,当然这是你的决定,只是不要因为错误的理由(比如政治正确之类的)而回避它,否则会使投票系统变得荒谬。 - Christian Rau

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