我只用了几个星期才开始写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如何分配内存?什么是默认值?
简答:它不是为你分配的.
稍长的答案:该subcells
指针是未初始化的并且可能指向任何地方.这是一个错误,你永远不应该让它发生.
更长的答案:自动变量在堆栈上分配,全局变量由编译器分配,并且通常占用特殊段或可能在堆中.默认情况下,全局变量初始化为零.自动变量没有默认值(它们只是获取在内存中找到的值),程序员负责确保它们具有良好的起始值(尽管许多编译器会在您忘记时尝试提示您).
newCell
函数中的变量是自动的,并且未初始化.你应该解决这个问题.要么newCell.subcells
及时给出有意义的值,要么指向它,NULL
直到为它分配一些空间.这样,如果在为其分配一些内存之前尝试取消引用它,则会抛出分段违规.
更糟糕的是,您将返回一个Cell
by值,但Cell *
在尝试填充subcells
数组时将其分配给a .返回指向堆分配对象的指针,或将值分配给本地分配的对象.
对于这个通常的习惯用法会有类似的形式
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; isubCells[i] = makeCell(dim-1); // what error checking do you need here? // depends on your other error checking... } return newCell; }
虽然我已经给你留下了一些问题要敲门.
请注意,您必须跟踪最终需要解除分配的所有内存位...
指针没有默认值.您的指针将指向当前存储的任何内容.因为你没有初始化它,所以
newCell.subcells[i] = ...
有效地访问内存的某些不确定部分.请记住,subcells [i]相当于
*(newCell.subcells + i)
如果左侧包含一些垃圾,您将最终添加i
到垃圾值并访问该不确定位置的内存.正如您所说,您必须初始化指针以指向一些有效的内存区域:
newCell.subcells = malloc(bytecount)
在哪一行之后,您可以访问那么多字节.关于其他内存来源,有不同类型的存储都有其用途.你得到什么样的取决于你拥有什么样的对象以及你告诉编译器使用哪个存储类.
malloc
返回指向没有类型的对象的指针.您可以使指针指向该内存区域,并且对象的类型将有效地成为指向对象类型的类型.内存未初始化为任何值,访问通常较慢.这样获得的对象被称为allocated objects
.
您可以全局放置对象.他们的记忆将被初始化为零.对于点,您将获得NULL指针,对于浮点数,您也将获得正确的零.您可以依赖正确的初始值.
如果您有局部变量但使用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
树中的每个指针释放该内存.它只是作为练习你写的:)