C的动态数组的详细知识(网上收集到的大量详细知识以及个人理解的汇总)

mac2025-05-19  30

动态数组是指在声明时没有确定数组大小的数组,即忽略圆括号中的下标;当要用它时,可随时用ReDim语句重新指出数组的大小。使用动态数组的优点是可以根据用户需要,有效利用存储空间。

可以了解动态数组的详细定义

一.C版本动态数组用到的函数及案例

(1)malloc

函数原型:extern void *malloc(unsigned int num_bytes);

头文件

#include <stdlib.h> 或者 #include <malloc.h>

函数申明

void *malloc(size_t size);

备注:void* 表示未确定类型的指针,void *可以指向任何类型的数据,更明确的说是指申请内存空间时还不知道用户是用这段空间来存储什么类型的数据 (比如是char还是int或者其他数据类型)。

与new区别

malloc 则必须要由我们计算字节数,并且在返回后强行转换为实际类型 的指针。 第一、malloc 函数返回的是 void * 类型。 对于C++,如果你写成:p = malloc (sizeof(int)); 则程序无法通过编译, 报错:“不能将 void* 赋值给 int * 类型变量”。 所以必须通过 (int *) 来将强制转换。而对于C, 没有这个要求,但为了使C程序更方便的移植到C++中来, 建议养成强制转换的习惯。 第二、函数的实参为 sizeof(int) ,用于指明一个整型数据需要的大小。 在Linux中可以有这样:malloc(0),这是因为Linux中malloc有一个下限值16Bytes,注意malloc(-1)是禁止的;但是在某些系统中是不允许malloc(0)的。 在规范的程序中我们有必要按照这样的格式去使用malloc及free:

malloc函数工作机制

malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。

malloc()到底从哪里得到了内存空间?答案是从堆里面获得空间。也就是说函数返回的指针是指向堆里面的一块内存。操作系统中有一个记录空闲内存地址的链表。当操作系统收到程序的申请时,就会遍历该链表,然后就寻找第一个空间大于所申请空间的堆结点,然后就将该结点从空闲结点链表中删除,并将该结点的空间分配给程序。

malloc()在运行期动态分配分配内存,free()释放由其分配的内存。malloc()在分配用户传入的大小的时候,还分配的一个相关的用于管理的额外内存,不过,用户是看不到的。所以,

实际的大小 = 管理空间 + 用户空间

(2)calloc函数

其原型 void *calloc(size_t n, size_t size);

先看一下案例,找与malloc区别

案例一:

#include<stdio.h> #include<stdlib.h> int main(){ int *p = (int *)malloc(20*sizeof(int)); int *pp = (int *)calloc(20, sizeof(int)); int i; printf("malloc申请的空间值:\n\n"); for ( i=0 ; i < 20; i++) { printf("%d ", *p++); } printf("\n\n"); printf("calloc申请的空间的值:\n\n"); for ( i=0 ; i < 20; i++) { printf("%d ", *pp++); } printf("\n"); printf("原来的p_Address:%x 扩容后的pp_Address:%x \n\n", p, pp); free(p); free(pp); }

结果有时是

有时是

区分从定义开始

(1)malloc函数。其原型void *malloc(unsigned int num_bytes); num_byte为要申请的空间大小,需要我们手动的去计算,如int *p = (int )malloc(20sizeof(int)),如果编译器默认int为4字节存储的话,那么计算结果是80Byte,一次申请一个80Byte的连续空间,并将空间基地址强制转换为int类型,赋值给指针p,此时申请的内存值是不确定的。

(2)calloc函数,其原型void *calloc(size_t n, size_t size); 其比malloc函数多一个参数,并不需要人为的计算空间的大小,比如如果他要申请20个int类型空间,会int *p = (int *)calloc(20, sizeof(int)),这样就省去了人为空间计算的麻烦。但这并不是他们之间最重要的区别,malloc申请后空间的值是随机的,并没有进行初始化,而calloc却在申请后,对空间逐一进行初始化,并设置值为0; 【返回值】分配成功返回指向该内存的地址,失败则返回 NULL。

char *ptr = (char *)calloc(10, 10); // 分配100个字节的内存空间 2中等效的方法 // calloc() 分配内存空间并初始化 char *str1 = (char *)calloc(10, 2); // malloc() 分配内存空间并用 memset() 初始化 char *str2 = (char *)malloc(20); memset(str2, 0, 20);

<1>可从图看出,扩容后地址和原先地址是不一样的,但是这仅仅取决于扩容的内存大小。 <2>calloc函数由于给每一个空间都要初始化值,那必然效率较malloc要低,并且现实世界,很多情况的空间申请是不需要初始值的,这也就是为什么许多初学者更多的接触malloc函数的原因。

<3>实际上: 如果size较小,原来申请的动态内存后面还有空余内存,系统将直接在原内存空间后面扩容,并返回原动态空间基地址;如果size较大,原来申请的空间后面没有足够大的空间扩容,系统将重新申请一块(20+size)*sizeof(int)的内存,并把原来空间的内容拷贝过去,原来空间free;如果size非常大,系统内存申请失败,返回NULL,原来的内存不会释放。注意:如果扩容后的内存空间较原空间小,将会出现数据丢失,如果直接realloc(p, 0);相当于free§.

想了解更多,请点

(3)free函数

调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。如果无法获得符合要求的内存块,malloc函数会返回NULL指针,因此在调用malloc动态申请内存块时,一定要进行返回值的判断。

(4)realloc函数

realloc函数用于修改一个原先已经分配的内存块的大小,可以使一块内存的扩大或缩小。

当起始空间的地址为空,即ptr = NULL,则同malloc。当ptr非空:若new_size < size,即缩小*ptr所指向的内存空间,该内存块尾部的部分内存被拿掉,剩余部分内存的原先内容依然保留;

若new_size > size,即扩大*ptr所指向的内存空间,如果原先的内存尾部有足够的扩大空间,则直接在原先的内存块尾部新增内存

如果原先的内存尾部空间不足,或原先的内存块无法改变大小,realloc将重新分配另一块new_size大小的内存,并把原先那块内存的内容复制到新的内存块上。因此,使用realloc后就应该改用realloc返回的新指针。

补充

如果需要在用malloc后初始化置零,使用memset函数 void *memset(void *s, int ch, size_t n);

借鉴其他更多关于malloc的解释

malloc关于内存的详细

三.总结:

//动态存储分配函数 1.void *malloc(unsigned size)分配空间,该函数分配了size个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。 通过malloc函数得到的堆内存必须使用memset函数来初始化。 2.void free(void *p) 释放p所指的空间 3. void * realloc(void *p,unsigned size) 改空间大小 4.void *calloc(unsigned n, unsigned size) //分配空间, 元素初始化为0 ## malloc和calloc有点关系 ##

案例二:使用malloc和realloc生成一、二维动态数组

#include<stdio.h> #include<stdlib.h> int main(){ //1.一维动态数组 int * p=(int *) malloc(10*sizeof(int)); if(p!=NULL) { //申请了内存空间后,必须检查是否分配成功。 for(int i=0;i<11;i++) p[i]=i; for(int i=0;i<10;i++) printf("%-4d",p[i]); }else{ printf("mallocerror!\n"); exit(-1); } free(p);p=NULL; //该函数是将之前用malloc分配的空间还给程序或者是操作系统, //也就是释放了这块内存,让它重新得到自由。 //2.二维动态数组(在主函数外面作为全局数组) const int max = 3; int ** maze = (int **) malloc (max * sizeof(int*)); for (int i = 0; i < max; i++) { maze[i] = (int *)malloc(max * sizeof(int)); } int length, width; printf("input the width and length\n" ); scanf("%d %d",&width,&length); //##运用realloc修改大小,变大变小都可以,最后还可以被free()释放 maze = (int **) realloc (maze,width * sizeof(int*)); for (int i = 0; i < width; i++) { maze[i] = (int *)realloc(maze[i],length * sizeof(int)); } for (int i = 0; i < width; i++) { for (int j = 0; j < length; j++) { scanf("%d",& maze[i][j]) ; } } printf( "graph \n" ); for (int i = 0; i < width; i++) { for (int j = 0; j < length; j++) { printf("%-4d",maze[i][j]); } printf("\n"); } free(maze); }

最后

下面是使用动态分配的内存的基本规则:

●避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大内存块的系统开销大。

●仅在需要时分配内存。只要使用完堆上的内存块,就释放它。

●总是确保释放已分配的内存。在编写分配内存的代码时,就要确定在代码的什么地方释放内存。

●在释放内存之前,确保不会无意中覆盖堆上分配的内存的地址,否则程序就会出现内存泄漏。在循环中分配内存时,要特别小心。

参考: https://www.baidu.com/link?url=fJ5P3mDdJxPJPBMMaMXI6v46fW-leyqoYE3FYjm-BbOWJMH3ktv2btFO6N77IpjPS1TFEaaN10FSRqjxMsFSBa&wd=&eqid=c9a766c900011608000000065dbbff90

https://blog.csdn.net/weibo1230123/article/details/81503135

https://www.baidu.com/link?url=fJ5P3mDdJxPJPBMMaMXI6v46fW-leyqoYE3FYjm-BbOWJMH3ktv2btFO6N77IpjPS1TFEaaN10FSRqjxMsFSBa&wd=&eqid=c9a766c900011608000000065dbbff90

https://blog.csdn.net/chf_1/article/details/78688557

https://www.baidu.com/link?url=W8raR9LQw37BYWkrFig6beLI5-gWA9ZLqOky8bHi08D-r9SncdUXftdpg5kai9AMiFCgz3Vsxke82eg_gVLYF3J8tjEKE2pKCZKL0L0GnD6uJW_LEKID33rvHV8G7i2k&wd=&eqid=e955c01500002a99000000065dbbfe8c

https://www.cnblogs.com/tangshiguang/p/6735402.html

最新回复(0)