我正在努力学习malloc()/ free()的C规则。考虑下面的代码,运行得很好。 (在Linux上,使用GCC编译。)具体来说,我想知道arr
数组。我得知你必须在数组中的所有结构元素中使用malloc ... 但为什么你不必为数组提供malloc 本身 ?
#include<stdio.h>
#include<stdlib.h>
#define ARR_SIZE 100
typedef struct containerStruct{
int data1;
int data2;
int data3;
// ...etc...
} container;
int main(){
container* arr[ARR_SIZE]; // Don't I have to malloc this?
int i;
for(i=0; i<ARR_SIZE; i++){
arr[i] = (container*) malloc (sizeof(container)); // I get why I have to malloc() here
}
...do stuff...
for(i=0; i<ARR_SIZE; i++){
free(arr[i]); // I get why I have to free() here
}
// Don't I have to free(arr) ?
return 0;
}
我猜测container* arr[ARR_SIZE];
行告诉编译器需要知道如何在内存中为数组划分空间,这就是为什么这段代码可以工作。
但它仍然没有“感觉”正确,不知何故。当我malloc()的时候,我正在堆上保留内存空间,对吧?但我的container* arr[ARR_SIZE];
调用会在堆栈上创建数组。所以...容器*搬运工的数组存在于堆栈中,所有这些容器*指针指向堆上的容器结构。这是对的吗?
答案 0 :(得分:4)
在arr
声明的正下方,您有以下内容:
int i;
保留足够的空间(可能在“堆栈”上)来存储int
。这对你“感觉”有什么不对吗?
arr
的声明也不例外。编译器为ARR_SIZE
的{{1}}元素数组分配了足够的空间(可能在“堆栈”上)。
答案 1 :(得分:4)
当我使用malloc()时,我正在保留堆上的内存空间,对吗?
不要说,堆是一个实现细节。你是动态分配内存。您还有责任再次动态释放它。
但是我的容器* arr [ARR_SIZE];调用
这不是电话,而是宣言。
在堆栈上创建数组。
也不要说,堆栈也是一个实现细节。
你声明一个带有自动范围的本地(这里是一个指针数组),编译器负责管理它的内存和生命周期。
它的生命周期不是动态的,因为一旦到达封闭块末尾的}
就会无法访问它,因此编译器可以确定地为你处理它。
所有局部变量的行为都相同:
{
int i;
double d[2];
} /* neither i nor d are reachable after here,
so the compiler takes care of releasing any storage */
所以...容器*指针数组存在于堆栈中,
考虑更简单的声明container c;
。这是具有自动范围的本地,编译器负责(de)分配,如上所述。
现在考虑container *p;
。这也是 一个具有自动范围的局部变量,但该变量是一个指针。您仍然需要手动将指向某些内容,如果您指向的内容是从malloc
返回的,则您需要自己free
。
进一步考虑一个简单的数组container a[2];
。现在您有一个具有自动范围的本地数组,其中包含container
类型的两个实例。两个实例都具有相同的生命周期,它仍然由编译器管理。您可以访问a[0]
和a[1]
,其他任何内容都是非法的。
并且所有这些容器*指针指向堆上的容器结构。这是对的吗?
没有。最后考虑container* ap[2]
。我们再次拥有一个具有自动范围的本地数组,其中包含两个指针。指针的类型是指向容器的指针,指针的生命周期由编译器管理。但是,指针在任何地方都没有指向,并且你有责任决定将它们指向什么,并找出指向的生命周期事情有。
一旦你做了
ap[i] = malloc(sizeof(*ap[i]));
(你不需要强制转换,并且通常更安全地避免明确命名类型,以防你以后更改它并分配错误的大小),你已经分配了一个你负责释放的对象。你还在这个对象中指出了数组中的一个指针,但这并没有以某种方式改变指针本身的生命周期。该数组就像通常的 指针一样超出范围。
答案 2 :(得分:3)
您可以选择以下任一选项:
int i;
container** arr = malloc(sizeof(container*)*ARR_SIZE);
for (i=0; i<ARR_SIZE; i++)
arr[i] = malloc(sizeof(container));
// do stuff...
for(i=0; i<ARR_SIZE; i++)
free(arr[i]);
free(arr);
int i;
container* arr[ARR_SIZE];
for (i=0; i<ARR_SIZE; i++)
arr[i] = malloc(sizeof(container));
// do stuff...
for(i=0; i<ARR_SIZE; i++)
free(arr[i]);
container arr[ARR_SIZE];
// do stuff...
由于ARR_SIZE
是常量,您也可以选择最后一个选项。
答案 3 :(得分:2)
container* arr[ARR_SIZE];
告诉编译器分配一个ARR_SIZE
类型 container*
元素的数组,编译器会相应地分配内存。
换句话说,这类似于int x[5] = 0;
,其中编译器为5 int
的数组分配足够的空间。在您的情况下,编译器为ARR_SIZE
个指针container*
分配了足够的空间,就是这样。现在,您可以将这些指针指向有效的内存位置。为此,您可以
所以,最重要的是,您不需要为数组分配任何内存。对于每个单独的数组元素,您需要使用内存分配器函数分配内存,因为您希望每个元素指向有效内存。
答案 4 :(得分:2)
由于ARR_SIZE
是动态数组*arr[ARR_SIZE]
的固定成员,因此您不再需要为整个数组分配内存,而只需为其中的元素分配内存,因为在这种情况下它们是灵活的。 / p>
当您malloc
任何类型的动态数组时,始终检查从void*
返回的malloc
指针的返回值是安全的。此外,当您free
每个元素时,将每个成员再次设置为NULL
甚至更安全,以防止指针再次访问内存。
通过此代码说明:
#include <stdio.h>
#include <stdlib.h>
#define ARR_SIZE 100
typedef struct {
int data1;
int data2;
int data3;
// ...etc...
} container_t;
int
main(void) {
container_t *arr[ARR_SIZE];
int i;
for (i = 0; i < ARR_SIZE; i++) {
arr[i] = malloc(sizeof(container_t));
if (arr[i] == NULL) {
printf("Malloc Problem here\n");
exit(EXIT_FAILURE);
}
}
for (i = 0; i < ARR_SIZE; i++) {
if (arr[i]) {
free(arr[i]);
arr[i] = NULL;
}
}
return 0;
}