C 语言内存管理实例

现实生活中的内存管理实例

为了展示动态内存的实际应用,我们编写了一个程序,该程序能够创建任意长度的列表。

C 语言中的常规数组具有固定长度且无法更改,但通过动态内存,我们可以创建任意长度的列表:

实例

struct list {  
  int *data; // 指向存储列表项的内存  
  int numItems; // 表示当前列表中的项数  
  int size; // 表示分配的内存可容纳的项数  
};  

void addToList(struct list *myList, int item);  

int main() {  
  struct list myList;  
  int amount;  

  // 创建一个列表,并初始分配足够容纳10个项的空间  
  myList.numItems = 0;  
  myList.size = 10;  
  myList.data = malloc(myList.size * sizeof(int));  

  // 检查内存分配是否成功  
  if (myList.data == NULL) {  
    printf("内存分配失败");  
    return 1; // 使用错误代码退出程序  
  }  

  // 向列表中添加由amount变量指定的任意数量的项  
  amount = 44;  
  for (int i = 0; i < amount; i++) {  
    addToList(&myList, i + 1);  
  }  

  // 显示列表内容  
  for (int j = 0; j < myList.numItems; j++) {  
    printf("%d ", myList.data[j]);  
  }  

  // 当内存不再需要时释放它  
  free(myList.data);  
  myList.data = NULL;  

  return 0;  
}  

// 此函数向列表中添加一个项  
void addToList(struct list *myList, int item) {  

  // 如果列表已满,则调整内存大小以容纳更多10个项  
  if (myList->numItems == myList->size) {  
    myList->size += 10;  
    myList->data = realloc(myList->data, myList->size * sizeof(int));  
  }  

  // 将项添加到列表末尾  
  myList->data[myList->numItems] = item;  
  myList->numItems++;  
}

亲自试一试

结构体指针:此例中有一个指向结构体 myList 的指针。因为我们使用的是指向结构体的指针而不是结构体本身,所以我们使用箭头语法(->)来访问结构体的成员。

例子解释

此例包含三个部分:

  • 包含列表数据的结构体 myList
  • 包含程序的主函数 main()
  • 向列表中添加项的函数 addToList()

myList 结构体

myList 结构体包含有关列表的所有信息,包括其内容。它有三个成员:

  • data:指向包含列表内容的动态内存的指针
  • numItems:表示列表中的项数
  • size:表示分配的内存可容纳的项数

我们使用结构体以便能够轻松地将所有这些信息传递给函数。

main() 函数

main() 函数首先初始化列表,为其分配足够容纳 10 个项的空间:

// 创建一个列表,并初始分配足够容纳10个项的空间  
myList.numItems = 0;  
myList.size = 10;  
myList.data = malloc(myList.size * sizeof(int));

myList.numItems 设置为 0,因为列表开始时为空。

myList.size 用于跟踪预留了多少内存。我们将其设置为 10,因为我们将为 10 个项预留足够的内存。

然后我们分配内存,并将指向它的指针存储在 myList.data 中。

接着,我们包含错误检查以确定内存分配是否成功:

// 检查内存分配是否成功  
if (myList.data == NULL) {  
  printf("内存分配失败");  
  return 1; // 使用错误代码退出程序  
}

如果一切正常,一个循环将使用 addToList() 函数向列表中添加 44 个项:

// 向列表中添加由 amount 变量指定的任意数量的项  
amount = 44;  
for (int i = 0; i < amount; i++) {  
  addToList(&myList, i + 1);  
}

在上面的代码中,&myList 是指向列表的指针,i + 1 是我们要添加到列表中的数字。我们选择 i + 1 以便列表从 1 开始而不是 0。您可以选择任何数字添加到列表中。

将所有项添加到列表后,下一个循环将打印列表的内容。

// 显示列表内容  
for (int j = 0; j < myList.numItems; j++) {  
  printf("%d ", myList.data[j]);  
}

打印完列表后,我们释放内存以防止内存泄漏。

// 当内存不再需要时释放它  
free(myList.data);  
myList.data = NULL;

addToList() 函数

我们的 addToList() 函数向列表中添加一个项。它接受两个参数:

void addToList(struct list *myList, int item)
  • 指向列表的指针
  • 要添加到列表中的值

该函数首先通过比较列表中的项数与列表的大小来检查列表是否已满。如果列表已满,则重新分配内存以容纳更多 10 个项:

// 如果列表已满,则调整内存大小以容纳更多10个项  
if (myList->numItems == myList->size) {  
  myList->size += 10;  
  myList->data = realloc(myList->data, myList->size * sizeof(int));  
}

最后,该函数将项添加到列表末尾。myList->numItems 处的索引始终位于列表末尾,因为每次添加新项时它都会增加1。

// 将项添加到列表末尾  
myList->data[myList->numItems] = item;  
myList->numItems++;

为什么我们每次预留 10 个项的空间?

优化是在内存和性能之间的平衡。尽管我们可能会分配一些未使用的内存,但频繁地重新分配内存可能效率低下。在分配过多内存和频繁分配内存之间存在平衡。

在此例中,我们选择了数字 10,但这取决于您预计的数据量和数据更改的频率。例如,如果我们事先知道我们将有 44 个项,那么我们可以一次性分配足够容纳 44 个项的内存。