我只用了很短的几个星期才开始写C而且没有花时间过分担心malloc()
。最近,我的一个程序返回了一串幸福的面孔,而不是我期望的真实/假值。
如果我创建这样的结构:
typedef struct Cell {
struct Cell* subcells;
}
然后稍后将其初始化为
Cell makeCell(int dim) {
Cell newCell;
for(int i = 0; i < dim; i++) {
newCell.subcells[i] = makeCell(dim -1);
}
return newCell; //ha ha ha, this is here in my program don't worry!
}
我最终会在某个地方访问存储在内存中的快乐面孔,或者可能是在先前存在的单元格中写入,还是什么?我的问题是,当我没有实际malloc()编辑适当的内存量时,C如何分配内存?什么是默认值?
答案 0 :(得分:26)
简答:没有为您分配。
答案稍长: subcells
指针未初始化,可能指向任何地方。这是一个错误,你永远不应该允许它发生。
更长的答案:自动变量在堆栈上分配,全局变量由编译器分配,并且通常占用特殊段或可能在堆中。默认情况下,全局变量初始化为零。自动变量没有默认值(它们只是获取内存中的值),程序员负责确保它们具有良好的起始值(尽管许多编译器会在您忘记时尝试提示您)。
您的函数中的newCell
变量是自动的,并且未初始化。你应该解决这个问题。要么及时给newCell.subcells
一个有意义的值,要么将它指向NULL
,直到为它分配一些空间。这样,如果您在为其分配内存之前尝试取消引用它,则会抛出分段违规。
更糟糕的是,您按值返回Cell
,但在尝试填充Cell *
数组时将其分配给subcells
。返回指向堆分配对象的指针,或将值赋给本地分配的对象。
通常的习惯用法就像
一样Cell* makeCell(dim){
Cell *newCell = malloc(sizeof(Cell));
// error checking here
newCell->subcells = malloc(sizeof(Cell*)*dim); // what if dim=0?
// more error checking
for (int i=0; i<dim; ++i){
newCell->subCells[i] = makeCell(dim-1);
// what error checking do you need here?
// depends on your other error checking...
}
return newCell;
}
虽然我已经给你留下了一些问题要敲定......
请注意,您必须跟踪最终需要解除分配的所有内存位...
答案 1 :(得分:17)
指针没有默认值。你的指针将指向它当前存储的任何内容。由于你没有初始化它,行
newCell.subcells[i] = ...
有效地访问内存的某些不确定部分。请记住,subcells [i]相当于
*(newCell.subcells + i)
如果左侧包含一些垃圾,您最终会将i
添加到垃圾值并访问该不确定位置的内存。正如您所说,您必须初始化指针以指向一些有效的内存区域:
newCell.subcells = malloc(bytecount)
在哪一行之后你可以访问那么多字节。关于其他内存来源,有不同类型的存储都有其用途。你得到什么样的取决于你拥有什么样的对象以及你告诉编译器使用哪个存储类。
malloc
返回指向没有类型的对象的指针。您可以使指针指向该内存区域,并且对象的类型将有效地成为指向对象类型的类型。内存未初始化为任何值,访问通常较慢。如此获得的对象称为allocated objects
。static
存储类说明符,那么您将具有与全局对象相同的初始值规则。内存通常以与全局对象相同的方式分配,但这绝不是必需的。 auto
的局部变量,那么你的变量将被分配在堆栈上(即使C没有定义,这当然是编译器实际做的)。您可以使用其地址,在这种情况下,编译器必须省略优化,例如将其放入寄存器中。 register
一起使用的局部变量标记为具有特殊存储。因此,您无法再获取其地址。在最近的编译器中,由于其复杂的优化器,通常不需要再使用register
。如果你真的是专家,那么如果使用它你可能会获得一些性能。 对象具有相关的存储持续时间,可用于显示不同的初始化规则(形式上,它们仅定义至少对象存活多长时间)。使用auto
和register
声明的对象具有自动存储持续时间,并且未已初始化。如果希望它们包含某些值,则必须显式初始化它们。如果不这样做,它们将包含编译器在开始生存之前留在堆栈中的任何内容。由malloc
(或该系列的其他功能,如calloc
)分配的对象已分配存储持续时间。他们的存储也未初始化。使用calloc
时例外,在这种情况下,内存初始化为零(“实际”为零。即所有字节为0x00,不考虑任何NULL指针表示)。使用static
声明的对象和全局变量具有静态存储持续时间。它们的存储 初始化为零,适合各自的类型。请注意,对象不能具有类型,但获取无类型对象的唯一方法是使用已分配的存储。 (C中的对象是“存储区域”)。
那又是什么?这是固定代码。因为一旦你分配了一块内存,就不能再找回你分配了多少项,最好总是在那里存储那个数。我已经在结构中引入了一个变量dim
来存储计数。
Cell makeCell(int dim) {
/* automatic storage duration => need to init manually */
Cell newCell;
/* note that in case dim is zero, we can either get NULL or a
* unique non-null value back from malloc. This depends on the
* implementation. */
newCell.subcells = malloc(dim * sizeof(*newCell.subcells));
newCell.dim = dim;
/* the following can be used as a check for an out-of-memory
* situation:
* if(newCell.subcells == NULL && dim > 0) ... */
for(int i = 0; i < dim; i++) {
newCell.subcells[i] = makeCell(dim - 1);
}
return newCell;
}
现在,对于dim = 2:
,事情看起来像这样Cell {
subcells => {
Cell {
subcells => {
Cell { subcells => {}, dim = 0 }
},
dim = 1
},
Cell {
subcells => {
Cell { subcells => {}, dim = 0 }
},
dim = 1
}
},
dim = 2
}
请注意,在C中,函数的返回值不需要是对象。根本不需要存储。因此,您不能更改它。例如,以下是不可能的:
makeCells(0).dim++
您将需要一个“自由功能”,可以再次释放已分配的内存。因为未自动释放已分配对象的存储空间。您必须调用free
为树中的每个subcells
指针释放该内存。作为练习,你可以写下来:)
答案 2 :(得分:4)
堆上没有分配任何内容(通过malloc
和类似的调用),而是在堆栈上分配。因此,当函数结束时,在特定函数中创建而不是malloc
的任何东西都将被销毁。包括返回的对象;当函数调用后展开堆栈时,返回的对象被复制到调用函数在堆栈中为它预留的空间。
警告:如果要返回一个指向其中对象的对象,请确保在堆上创建指向的对象,更好的是,在该对象上创建该对象。堆也是,除非它不打算在创建它的函数中存活。
答案 3 :(得分:3)
我的问题是,当我实际上没有malloc()编辑适当的内存量时,C如何分配内存?什么是默认值?
不分配内存。您必须明确地在堆栈上创建它或动态地创建它。
在您的示例中,子单元指向未定义位置,这是一个错误。您的函数应该在某个时刻返回指向Cell结构的指针。
答案 4 :(得分:0)
局部变量在堆栈上“分配”。堆栈是预分配的内存量,用于保存这些局部变量。当函数退出时变量不再有效,并且将被接下来的任何内容覆盖。
在您的情况下,代码无效,因为它不会返回您的结果。此外,当范围退出时,指向堆栈上对象的指针也将停止有效,所以我想在你的确切情况下(你似乎在做一个链表),你需要使用malloc()。
答案 5 :(得分:0)
我最终会在某个地方访问存储在内存中的快乐面孔,或者可能是在先前存在的单元格上写字,还是什么?
你很幸运,你有一张幸福的脸。在其中一个不幸的日子里,它可能会擦干你的系统;)
我的问题是,当我实际上没有malloc()编辑适当的内存量时,C如何分配内存?
没有。但是,当你定义Cell newCell时,会发生什么,subCells指针被初始化为垃圾值。这可能是0(在这种情况下你会崩溃)或一些足够大的整数使它看起来像一个实际的内存地址。在这种情况下,编译器会愉快地获取驻留在那里的任何值并将其带回给您。
默认是什么?
如果您没有初始化变量,这是 行为。并且您的makeCell
函数看起来有点欠发达。
答案 6 :(得分:0)
实际上可以分配三个部分 - 数据,堆栈和数据。堆。
在你提到的情况下,它将被分配在堆栈上。在堆栈上分配内容的问题在于它仅在函数持续时间内有效。函数返回后,将回收该内存。因此,如果返回指向堆栈上分配的内容的指针,则该指针将无效。如果您返回实际对象(不是指针),则会自动生成该对象的副本以供调用函数使用。
如果您已将其声明为全局变量(例如,在头文件中或函数外部),则会将其分配给内存的数据部分。本节中的内存在程序启动时自动分配,并在完成后自动解除分配。
如果你使用malloc()在堆上分配一些东西,那么只要你想使用它,那个内存就是好的 - 直到你调用free(),然后释放它。这使您可以根据需要灵活地分配和释放内存(而不是使用全局,其中所有内容都预先分配,只有在程序终止时才会释放)。
答案 7 :(得分:0)
我打算假装我是这里的电脑,阅读这段代码......
typedef struct Cell {
struct Cell* subcells;
}
这告诉我:
它不会告诉我指针是指向一个Cell还是一个Cell数组。在创建新Cell时,该指针的值未定义,直到为其分配值。在定义它们之前使用指针是坏消息。
Cell makeCell(int dim) {
Cell newCell;
新的Cell结构,带有未定义的子单元指针。所有这一切都保留了一小块内存,称为newCell,它是Cell结构的大小。它不会改变那个记忆中的值 - 它们可能是任何东西。
for(int i = 0; i < dim; i++) {
newCell.subcells[i] = makeCell(dim -1);
为了获得newCell.subcells [i],计算是从子元素偏移i,然后是 dereferenced 。具体来说,这意味着从该内存地址中提取值。举例来说,i == 0 ...然后我们将取消引用子单元指针本身(没有偏移量)。由于子单元未定义,它可以是任何东西。字面意思!所以,这会要求内存中某个地方完全随机的值。结果无法保证任何结果。它可能打印一些东西,它可能会崩溃。绝对不应该这样做。
}
return newCell;
}
每次使用指针时,确保在取消引用它之前将其设置为值非常重要。鼓励你的编译器给你任何警告,许多现代编译器都可以捕获这类东西。你也可以指出像0xdeadbeef这样可爱的默认值(呀!这是一个十六进制的数字,它也只是一个单词,所以它看起来很有趣),这样它们就能脱颖而出。 (printf的%p选项有助于显示指针,作为调试的粗略形式。调试程序也可以很好地显示它们。)