为什么memset失败而calloc成功?

问题描述 投票:0回答:5

我正在尝试初始化一个包含 26 个字符串的数组。我不希望将数组放在堆上,但是当我尝试使用

memset
将内存分配给数组时出现分段错误。重现此代码如下:

char *string_array[26];
for (int x = 0; x < 26; x++)
    memset(string_array[x], 0, 3 + x); //= calloc(3+x, sizeof(char));

如果我更改代码以便将内存分配给堆,使用

calloc
,我不会出现分段错误:

char *string_array[26];
for (int x = 0; x < 26; x++)
    string_array[x] = calloc(3 + x, sizeof(char));

为什么会这样?

arrays c dynamic-memory-allocation c-strings calloc
5个回答
2
投票

char *string_array[26];
仅在堆栈上分配一个指针数组。

然后使用 memset 初始化指针指向的内容是未定义的行为,因为该内存尚未分配。


2
投票

我假设 OPs 实现实际上有一个堆和一个栈

是否可以在栈上动态分配这些指针?

指针已经在栈上了。如果你问的是你的指针指向的内存,那么是的。 C 有可变长度数组(简称 VLA)——但是你会遇到范围问题。自动变量(例如 VLA)在其声明范围的末尾被销毁。

for (int x = 0; x < 26; x++) {
    char myvla[3 + x];          // assuming a stack exists, that's where this goes
                                // use myvla as you please here.
}                               // <- and here it goes away

如果您希望在堆栈上动态分配的内存超出直接范围并持续到函数返回,您可以(!)使用

alloca
/
_alloca
。这是一个在堆栈上进行动态内存分配的非标准函数。

它通常以以下形式之一出现:

void *alloca(size_t size);
void *_alloca(size_t size);

例子:

#include <alloca.h>
#include <string.h>

int main() {
    char *string_array[26];

    for (int x = 0; x < 26; x++) {
        string_array[x] = alloca(3 + x); // stack allocation
    }

    // use afterwards
    for (int x = 0; x < 26; x++) {
        memset(string_array[x], 0, 3 + x);
    }
} // here the stack allocated memory is freed - do not use after this

1
投票

在使用

calloc
的代码片段中,您创建了 27 个数组:自动存储中的一个指针数组[1],以及堆上的 26 个字符数组。

在使用

memset
的代码片段中,您创建了指针数组,仅此而已。您尝试修改 26 个字符数组,但您从未创建它们。

以下将等同于 calloc 片段,但仅使用自动存储。

char string00[  3 ] = { 0 };
char string01[  4 ] = { 0 };
char string02[  5 ] = { 0 };
char string03[  6 ] = { 0 };
char string04[  7 ] = { 0 };
char string05[  8 ] = { 0 };
char string06[  9 ] = { 0 };
char string07[ 10 ] = { 0 };
char string08[ 11 ] = { 0 };
char string09[ 12 ] = { 0 };
char string10[ 13 ] = { 0 };
char string11[ 14 ] = { 0 };
char string12[ 15 ] = { 0 };
char string13[ 16 ] = { 0 };
char string14[ 17 ] = { 0 };
char string15[ 18 ] = { 0 };
char string16[ 19 ] = { 0 };
char string17[ 20 ] = { 0 };
char string18[ 21 ] = { 0 };
char string19[ 22 ] = { 0 };
char string20[ 23 ] = { 0 };
char string21[ 24 ] = { 0 };
char string22[ 25 ] = { 0 };
char string23[ 26 ] = { 0 };
char string24[ 27 ] = { 0 };
char string25[ 28 ] = { 0 };
char *string_array[ 26 ] = {
   string00, string01, string02, string03, string04,
   string05, string06, string07, string08, string09,
   string10, string11, string12, string13, string14,
   string15, string16, string17, string18, string19,
   string20, string21, string22, string23, string24,
   string25,
};

现在,如果我们假设

char
没有对齐限制,我们可以将上面的内容简化如下:

char buffer[ 403 ] = { 0 };  // 3+4+5+...+28 = 403
char *string_array[ 26 ];
for ( size_t j=0, i=0; j<26; ++j ) {
   string_array[ j ] = buffer + i;
   i += j + 3;
}

最后,正如@Ted Lyngmo 指出的那样,一些编译器提供

alloca
在自动存储中分配。

char *string_array[ 26 ];
for ( size_t j=0; j<26; ++j ) {
   string_array[ j ] = alloca( j + 3 );       // This is what you were missing.
   memset( string_array[ j ], 0, j + 3 );
}

  1. 不能保证使用堆栈。但是,是的,它可能会是一堆。

1
投票

Memset 用于用特定值填充一块内存。通常 memset 用于在重新使用块之前使用默认值重置内存块。 Memset 不是内存分配(即 calloc 和 malloc)的替代品。这就是您的第一个代码段由于在未分配的内存范围内设置值而失败的原因,因为第二个代码段实际上是在分配内存。


1
投票
char *string_array[26];

声明一个指向

char
的指针数组[26]。请注意,这只会为指针分配内存,而不是指向数据的内存。

memset()函数填充内存区的前n个字节 由 s 指向常量字节 c.

您的代码调用未定义的行为,因为它正在写入不属于它的内存。指针的内容是不确定的,即它们可能指向任何东西,进程访问指针指向的内存是不合法的。

calloc()
,另一方面,用于动态分配内存。

calloc() 函数应为数组分配未使用的空间 nelem 元素,每个元素的字节大小为 elsize。空间 应初始化为所有位 0.

© www.soinside.com 2019 - 2024. All rights reserved.