memset如何通过-1来初始化整数数组?

66

manpage中关于memset的说明:

#include <string.h>
void *memset(void *s, int c, size_t n)

The memset() function fills the first n bytes of the memory area pointed to by s with the constant byte c.

很明显,memset 不能用于像下面展示的初始化 int 数组:
int a[10];
memset(a, 1, sizeof(a));  

这是因为int类型使用4个字节来表示(假设),在数组a中无法得到所需的整数值。
但我经常看到程序员使用memset函数将int数组元素设置为0-1

int a[10];
int b[10];
memset(a, 0, sizeof(a));  
memset(b, -1, sizeof(b));  

据我的理解,使用整数0进行初始化是可以的,因为0可以用1字节表示(在这种情况下可能我是错误的)。但如何将b初始化为-1(一个4字节的值)呢?

你对使用0进行初始化的原因有些错误。这样做是可以的,因为0适合于unsigned char(所以在作为memset的第二个参数时不会被截断),并且因为sizeof(int)字节零的位模式与sizeof(int)个一字节零的位模式相同。这两个条件必须同时满足才能起作用。实际上,在二进制补码算术中,只有两个数字符合这些条件:0-1 - zwol
@zwol:嗯?第一句话提到了零,因此对于-1来说并不是字面上的真实情况。所以你可能想要隐含地给第一句话加上参数:如果一个int类型的值x的位与一个sizeof(int)unsigned char类型的值相同,且这些unsigned char类型的值都等于x,那么它就适用于x。此外,我们必须将值为xunsigned char视为从x转换而来,因为-1无法表示。如果是这样的话,那么0和-1并不是唯一的这样的值。16,843,009 • x适用于任何整数0 ≤ x < 256。(16,843,009是十六进制1010101)。 - Eric Postpischil
@EricPostpischil 哦,你依赖于对 memset 的第二个参数的内部截断。我认为这是作弊,因为如果不考虑与传统 C 的向后兼容性,memset 将采用 unsigned char 第二个参数。 - zwol
@zwol:但是您使用了-1。memset需要一个int,将其转换为unsigned char,并将其复制到每个字节中。-1不能表示为“unsigned char”;它会被转换为UCHAR_MAX。因此,如果您允许,那么0x34343434(或类似值,对于更大的字节C实现)将以同样的方式工作。 - Eric Postpischil
1
@zwol:memset是基于unsigned char定义的。在发布的问题或C规范中不存在signed charchar - Eric Postpischil
显示剩余4条评论
2个回答

74
奇怪的是,使用-1的原因与使用零的原因完全相同:在二进制补码表示法中,无论整数的大小如何,-1的所有位都是1,因此填充一个区域的字节与所有1的产生的结果是一个由-1有符号的intlongshort组成的区域,在二进制补码硬件上工作时。
在与二进制补码不同的硬件上,结果将会有所不同。-1整数常量将转换为一个unsigned char,该字符为全部为1。因为标准规定了转换的具体操作方式。然而,一个所有位都被设置为1的字节区域将根据平台的规则被解释为整数值。例如,在分别处理正负号的硬件上,数组的所有元素都将包含相应类型的最小负值。

17
使用 ~0 不是等效的吗(而且更清晰)? - Fiddling Bits
2
@FiddlingBits 是的,使用 ~0 绝对可以避免这里的混淆。 - Sergey Kalinichenko
2
如果你使用全1位模式填充与某个整数类型(例如int、long或short)的sizeof相关联的内存区域,然后将该区域重新解释为相应的整数类型,则在使用二进制补码表示法的计算机上将看到-1。请注意,在极少数情况下,当您拥有一种符号-大小硬件时,您会看到该硬件上可表示的最小负整数(我从未见过这样的硬件,甚至没有听说过看到这样的硬件的人)。 - Sergey Kalinichenko
2
标准规定,将负整数值转换为“unsigned”类型,编译器必须从2的N次方(其中N是无符号整型中位数的数量)中减去负值的幅度。在这里,N为8,所以结果是256-1=255,一个无符号值。这就是他们避免使该过程实现定义而又不需要2s补码表示的方式。这就是为什么我认为,“-1”将被转换为全为1的位模式,而不管负数在目标平台上的表示方式如何。 - Sergey Kalinichenko
1
哇,我之前没有意识到在非二进制补码机器上使用memset实际上需要进行int->unsigned转换(因为它在那里不是no-op)。我猜这可以追溯到早期的C历史(可能是在原型之前),因此不能声明为unsignedunsigned char,但奇怪的是,在汇编中调用者不一定传递它想要的位模式。 - Peter Cordes
显示剩余10条评论

8

当一个数字的所有位都是0时,它的值也为0。但是,如果所有位都是1,那么该值为-1

如果我们写int a[2],将分配4x2字节的内存,其中包含随机/垃圾位-

00110000 00100101 11100011 11110010    11110101 10001001 00111000 00010001

然后,我们写下memset(a, 0, sizeof(a))。现在,memset()按字节处理,一个字节的表示形式(unsigned char)为000000000。因此,它变成-

00000000 00000000 00000000 00000000    00000000 00000000 00000000 00000000

因此,a[0]a[1]都被初始化为0
现在,让我们看一下 memset(a, -1, sizeof(a)):一个字节的-111111111。 最终结果如下:
11111111 11111111 11111111 11111111    11111111 11111111 11111111 11111111

在这里,a [0]a [1] 的值都将是 -1


然而,对于memset(a,1,sizeof(a)):字节中的 1 00000001 -

00000001 00000001 00000001 00000001    00000001 00000001 00000001 00000001

因此,该值将为- 16843009

void *memset( void *dest, int ch, size_t count ); => 将值 ch(转换为 unsigned char 后)复制到指向 dest 对象的前 count 个字符中。 - Minhas Kamal

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