c# 为什么memset失败而calloc成功?

wfauudbj  于 2023-03-11  发布在  C#
关注(0)|答案(5)|浏览(201)

我试图初始化一个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));

为什么会这样?

cl25kdpy

cl25kdpy1#

char *string_array[26];只分配堆栈上的指针数组。
然后使用memset初始化指针所指向的内容是未定义的行为,因为该内存尚未分配。

1u4esq0p

1u4esq0p2#

我假设OP的实现实际上有一个堆和一个栈
是否可以在堆栈上动态地分配这些指针?

***指针***已经在堆栈上了。如果你问你的指针指向的内存,那么是的。C语言有可变长度数组(Variable Length Arrays,简称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
lf5gs5x2

lf5gs5x23#

在使用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/_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 );
}

C标准只讨论了 * 存储持续时间 *,它没有规定对象存储在哪里,只规定了可以访问对象的持续时间。
所以下面的结论并不是基于标准的,但下面的结论几乎是普遍正确的:
| 自动存储时间|分配存储持续时间|
| - ------|- ------|
| 更快|慢点|
| 更少的内存开销|更多内存开销|
| 对已分配数据块的生命周期的控制有限|生存期可以超出分配内存的函数|
| 过度分配的可怕后果|可以检测和处理过度分配|
| 资源有限|资源丰富|
| 随时可用|在某些系统上不可用(例如,许多嵌入式系统)|
| 除非编译器提供非标准的alloca或类似功能,否则很难/不可能分配复杂的结构。|易于分配复杂结构|
有两个储存期限未在此处涵盖:静态和线程。
1.不能保证一定会用到堆栈,但是,很可能会用到堆栈。

jm2pwxwz

jm2pwxwz4#

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

gcmastyq

gcmastyq5#

char *string_array[26];

声明了一个指向char的指针数组[26]。注意这只为指针分配内存,而不是指向的数据。
memset()函数用常量字节c填充s指向的内存区域的前n个字节。
你的代码调用了未定义的行为,因为它正在写入不属于它的内存。指针的内容是不确定的,也就是说,它们可能指向任何东西,进程访问指针所指向的内存是不法律的的。
另一方面,calloc()用于动态分配存储器。
calloc()函数应为nelem元素数组分配未使用的空间,每个元素的字节大小为elsize。该空间应初始化为所有位0。

相关问题