C会自动为我分配内存吗?

我几乎没有几个星期写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如何分配内存? 什么是默认值?

指针没有默认值。 您的指针将指向当前存储的任何内容。 因为你没有初始化它,所以行

 newCell.subcells[i] = ... 

有效地访问内存的某些不确定部分。 请记住,subcells [i]相当于

 *(newCell.subcells + i) 

如果左侧包含一些垃圾,您将最终将i添加到垃圾值并访问该不确定位置的内存。 正如您所说,您必须初始化指针以指向一些有效的内存区域:

 newCell.subcells = malloc(bytecount) 

在哪一行之后,您可以访问那么多字节。 关于其他内存来源,有不同类型的存储都有其用途。 你得到什么样取决于你拥有什么样的对象以及你告诉编译器使用哪个存储类。

  • malloc返回指向没有类型的对象的指针。 您可以使指针指向该内存区域,并且对象的类型将有效地成为指向对象类型的类型。 内存未初始化为任何值,访问通常较慢。 如此获得的allocated objects称为allocated objects
  • 您可以全局放置对象。 他们的记忆将被初始化为零。 对于点,您将获得NULL指针,对于浮点数,您也将获得正确的零。 您可以依赖适当的初始值。
  • 如果您有局部变量但使用static存储类说明符,那么您将具有与全局对象相同的初始值规则。 内存通常以与全局对象相同的方式分配,但这绝不是必需的。
  • 如果你有没有任何存储类说明符或auto局部变量,那么你的变量将被分配在堆栈上(即使C没有定义,这当然是编译器实际上做的)。 您可以使用其地址,在这种情况下,编译器必须省略优化,例如将其放入寄存器中。
  • 与存储类说明符register一起使用的局部变量标记为具有特殊存储。 因此,您无法再获取其地址。 在最近的编译器中,由于其复杂的优化器,通常不再需要使用register 。 如果你真的是专家,那么如果使用它你可能会获得一些性能。

对象具有相关的存储持续时间,可用于显示不同的初始化规则(forms上,它们仅定义至少对象存活多长时间)。 使用autoregister声明的对象具有自动存储持续时间并且初始化。 如果希望它们包含某些值,则必须显式初始化它们。 如果不这样做,它们将包含编译器在开始生存之前留在堆栈中的任何内容。 由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++ 

您将需要一个“自由function”,可以再次释放已分配的内存。 因为未自动释放已分配对象的存储空间。 您必须为树中的每个subcells指针释放该内存。 它只是作为练习你写的:)

简短回答:它不是为你分配的。

稍微长一点的答案: subcells指针未初始化,可能指向任何地方 。 这是一个错误, 永远不应该让它发生。

更长的答案:自动变量在堆栈上分配,全局变量由编译器分配,并且通常占用特殊段或可能在堆中。 默认情况下,全局变量初始化为零。 自动变量没有默认值(它们只是获取在内存中找到的值),程序员负责确保它们具有良好的起始值(尽管许多编译器会在您忘记时尝试提示您)。

函数中的newCell变量是自动的,并且未初始化。 你应该解决这个问题。 或者给newCell.subcells一个有意义的值,或者将它指向NULL直到你为它分配一些空间。 这样,如果您在为其分配一些内存之前尝试取消引用它,则会抛出分段违规。

更糟糕的是,您按值返回Cell ,但在尝试填充subcellsarrays时将其分配给Cell * 。 返回指向堆分配对象的指针,或将值分配给本地分配的对象。

通常的习惯用法就像是一样

 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; } 

虽然我已经给你留下了一些问题要敲门。

请注意,您必须跟踪最终需要解除分配的所有内存位…

未在堆上分配的任何内容(通过malloc和类似的调用)都会在堆栈上分配。 因此,当函数结束时,在特定函数中创建而不是malloc的任何东西都将被销毁。 那包括返回的对象; 当函数调用后展开堆栈时,返回的对象被复制到调用函数在堆栈上为它预留的空间。

警告:如果要返回一个指向其中对象的对象,请确保在堆上创建指向的对象,更好的是,在堆上创建该对象,除非它不是为了生存它创建的function。

我的问题是,当我没有实际malloc()编辑适当的内存量时,C如何分配内存? 什么是默认值?

不分配内存。 您必须明确地在堆栈上或动态创建它。

在您的示例中,子单元指向未定义的位置,这是一个错误。 您的函数应该在某个时刻返回指向Cell结构的指针。

我最终会在某个地方访问存储在内存中的快乐面孔,或者可能是在先前存在的单元格上写入,还是什么?

你很幸运,你有一张幸福的脸。 在其中一个不幸的日子里,它可能会擦干你的系统;)

我的问题是,当我没有实际malloc()编辑适当的内存量时,C如何分配内存?

它没有。 但是,当你定义Cell newCell时,会发生什么,subCells指针被初始化为垃圾值。 哪个可能是0(在这种情况下你会崩溃)或一些足够大的整数使它看起来像一个实际的内存地址。 在这种情况下,编译器会愉快地获取驻留在那里的任何值并将其带回给您。

什么是默认值?

如果您不初始化变量,则会出现这种情况。 而你的makeCellfunction看起来有点欠发达。

实际上可以分配三个部分 – 数据,堆栈和堆。

在你提到的情况下,它将被分配在堆栈上。 在堆栈上分配内容的问题在于它仅在函数的持续时间内有效。 函数返回后,将回收该内存。 因此,如果返回指向堆栈上分配的内容的指针,则该指针将无效。 如果您返回实际对象(不是指针),则会自动为该调用函数创建该对象的副本。

如果您已将其声明为全局变量(例如,在头文件中或在函数外部),则将其分配在内存的数据部分中。 本节中的内存在程序启动时自动分配,并在完成后自动释放。

如果你使用malloc()在堆上分配一些东西,那么只要你想使用它就会有好处 – 直到你调用free(),然后释放它。 这使您可以根据需要灵活地分配和释放内存(而不是使用全局,其中所有内容都预先分配,只有在程序终止时才会释放)。

局部变量在堆栈上“分配”。 堆栈是预分配的内存量,用于保存这些局部变量。 当函数退出时,变量将停止有效,并且将被接下来的任何内容覆盖。

在您的情况下,代码无效,因为它不会返回您的结果。 此外,当作用域退出时,指向堆栈上对象的指针也将停止有效,所以我想在您的确切情况下(您似乎在做链接列表),您将需要使用malloc()。

我打算假装我是这里的电脑,阅读这段代码……

 typedef struct Cell { struct Cell* subcells; } 

这告诉我:

  • 我们有一个名为Cell的结构类型
  • 它包含一个名为subcells的指针
  • 指针应该是struct Cell类型的东西

它没有告诉我指针是指向一个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,然后解除引用 。 具体来说,这意味着从该内存地址中提取值。 举例来说,i == 0 ...然后我们将取消引用子单元指针本身(没有偏移量)。 由于子单元未定义,它可以是任何东西。 字面意思! 所以,这会要求内存中某个地方完全随机的值。 结果无法保证。 它可能打印一些东西,它可能会崩溃。 绝对不应该这样做。

  } return newCell; } 

每次使用指针时,确保在取消引用之前将其设置为值非常重要。 鼓励你的编译器给你任何警告,许多现代编译器可以捕获这类事情。 你也可以指出像0xdeadbeef这样可爱的默认值(呀!这是一个hex的数字,它也只是一个单词,所以它看起来很有趣),这样它们就能脱颖而出。 (printf的%p选项有助于显示指针,作为调试的粗略forms。调试程序也可以很好地显示它们。)