为什么"memset(arr, -1, sizeof(arr)/sizeof(int))"不能将整数数组清零为-1?

50

在一个整数数组上使用 memset 不可行吗?我尝试了下面的 memset 调用,但在 int 数组中没有得到正确的整数值。

int arr[5];
memset (arr, -1, sizeof(arr)/sizeof(int));

我得到的值是:

arr[0] = -1
arr[1] = 255
arr[2] = 0
arr[3] = 0
arr[4] = 0

2
可能更容易的方式是这样做:int arr[5] = {-1}; - Thomas Dignan
37
除了将第一个元素初始化为-1,其余所有元素都初始化为0。 - tinman
6个回答

90

只需更改为memset (arr,-1,sizeof(arr));

注意,对于除0和-1以外的其他值,这种方法将不起作用,因为memset函数是设置从由*ptr变量指示的存储块开始的num字节的字节值。

void * memset ( void * ptr, int value, size_t num );

由于int在多个字节上表示,因此您将无法获得数组中整数的期望值。

例外情况:

  • 0是一个例外,因为如果您将所有字节设置为0,则该值将为零
  • -1是另一个例外,因为如Patrick所指出,-1在int8_t中为0xff(= 255),在int32_t中为0xffffffff

您之所以得到这个结果的原因是:

arr[0] = -1
arr[1] = 255
arr[2] = 0
arr[3] = 0
arr[4] = 0

因为在你的情况下,int类型长度为4个字节(32位表示),而数组的长度以字节计算为20(即5*4),而你只设置了5个字节为-1(=255),而不是全部20个字节。


7
在这种情况下(-1作为值),memset实际上很有效。因为在int8_t中,-1是0xff,在int32_t中则是0xffffffff等等。也就是说:memset对于0和-1是完全有效的,但对于所有其他情况并不是非常有用。 - Patrick B.
你是对的Patrick,谢谢。我已经相应地修改了我的答案。 - Ioan Paul Pirau
11
它可以在许多平台上正常工作,但并非所有平台都使用二进制补码,并且使用memset初始化一个int可能也会触发陷阱表示。 - Sander De Dycker
2
每个int,只要它的四个字节具有相同的值,就可以使用memset,而不仅仅是0和-1。 - phuclv
@LưuVĩnhPhúc 但是除了0之外的表示是否有保证呢?https://dev59.com/SGgu5IYBdhLWcg3ws5FC 对于0是肯定的,但我认为对于其他值不是这样的,因为我们不知道每个int中填充和符号位的位置。 - Ciro Santilli OurBigBook.com
你得到的结果:arr[0] = -1 arr[1] = 255 arr[2] = 0 arr[3] = 0 arr[4] = 0 是因为...不正确。你忘了一件重要的事情。由于arr没有用staticextern修饰并且显然是在一个函数内部声明(可能是 main()),它的元素值按照定义来说是不确定的。即使是对于arr[1],你也不能确定下三个字节里剩余位的值是什么。因此,打印a[1]a[4]的元素是明显的未定义行为。只有在 a[0]处你才能知道确切的结果。 - RobertS supports Monica Cellio

41

不要使用memset来初始化除单字节数据类型之外的任何其他数据类型。

乍一看,它似乎适用于将int初始化为0-1(在许多系统上,这确实会起作用),但是你没有考虑到可能会生成捕获表示形式从而导致未定义行为,以及整数表示形式不一定是二进制补码

将数组中的int初始化为-1的正确方法是循环遍历数组,并显式地设置每个值。


13
我认为这个答案应该加上一条附加条件,比如“如果你想编写绝对可移植的代码,请勿使用memset()函数”。大多数人既不写也不打算写可移植代码。只有在代码能够运行在两种架构上时,他们才会称其为“可移植”的代码。在“The correct way ...”中,“correct”这个词也可以改为“portable”。如果你并没有试图编写绝对可移植的代码,那么它就不是更正确或更不正确。 - Pascal Cuoq
9
@Complicated 请看个人简介 @Adam:问题在于使用循环初始化int数组在所有情况下都是可靠的,而使用memset可能无法正确地完成初始化(或者更糟的是,可能会看起来工作正常)。如果你对代码运行的平台有深入的了解,并知道它不会引起问题,那么你可以使用memset。但我并不知道每个可能阅读这篇回答的人的平台信息,所以我更喜欢采取安全措施。我希望这样能够平衡我在回答中使用的一些"极端"观点。 - Sander De Dycker
2
为Sander点赞,直到我意识到我使用的机器不是2补码,我才明白为什么-1对我不起作用。 - capitano666
@capitano666 - 只是好奇 - 你使用的处理器(“机器”)是什么? - Frederick
除了可移植参数之外,在我看来,“我知道这个有效”存在的问题是它隐藏了程序员的意图,依赖于一个假设:任何读取该代码的人必须知道如何解释该代码。 - John Z. Li
显示剩余12条评论

11

gcc提供了一种很好的数组初始化快捷方式

int arr[32] = {[0 ... 10] = 3, [11 ... 31] = 4}

注意在...之前和之后的空格


6
为什么要分开?
memset(arr, -1, sizeof(arr));

你的版本 sizeof(arr)/sizeof(int),给出了数组中元素的数量。

3
请注意,memset()设置的是所寻址位置的_字节_的值,而不是"项数"。 您需要将5个整数的字节设置为-1。 这样做恰好会以格式巧合的方式将整数值设置为-1 - Jeff Mercado
1
@Jeff:的确,这很巧合,因为一个-1的int通常是$FFFFFFFF(假设32位int和二进制补码),而一个-1的byte是$FF。如果他选择了-2($FE),那么它就会变成$FEFEFEFE,这是一个-16843010的int。 - Rudy Velthuis

4

您可以通过直接初始化数组来节省一些输入:

int arr[5] = {-1, -1, -1, -1, -1}; 

那行代码比memset短,而且也起作用。

虽然数组初始化器仅适用于少量值,但它对于加1操作非常有用。 - Martin Konicek
也可以缩写并省略元素数量的显式说明,因为编译器通过初始化的数量来检测元素的数量:int arr[] = {-1, -1, -1, -1, -1}; - RobertS supports Monica Cellio

1
void * memset ( void * ptr, int value, size_t num );

这个函数在大多数情况下适用于设置字符数组。它将指针ptr指向的内存块的前num个字节设置为指定值(解释为无符号字符)。memset-C++ Reference 它每次操作一个字节。因此,如果您将第二个参数分配为不超过0xff的int值,则可以正常工作。
至于您的版本,第三个参数是数组元素的数量,所以您得到了输出。 实际上,正确的做法是将第三个参数分配为您想要的字节数。
因此,正确的版本应该像这样:
memset (arr, -1, sizeof(arr));

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