文章目录
动态内存分配
我们要想实现动态内存分配,就需要学习标准C提供的函数库:
- 函数所属的库文件
- 函数的原型-函数的声明
- 函数名
- 形式参数
- 返回值类型
- 函数的功能
- 注意:内存分配函数在申请内存时,建议用多少申请多少,可以有少量的预留量;但不能越界访问(虽然编译运行不报错,但数据不安全)
一、常用函数
(一)malloc
1. 头文件:#include <stdlib.h>
2. 函数功能:
C库函数void *malloc(size_t size)
分配所需的内存空间,并返回一个指向它的指针。
3. 函数原型:
-
函数名:malloc
-
形式参数:size_t size:内存块的大小,以字节为单位。本质上就是一个无符号的整型
unsigned int
-
返回值类型:void*:该函数返回一个指针,指向已分配大小的内存,如果请求失败,返回NULL。
-
举例:
int *p = (int*)malloc(4);
-
说明:
- malloc函数分配的内存没有默认值,是不确定的数,大概率是0;
- malloc函数申请的内存空间连续。
(二)calloc
1. 头文件:#include <stdlib.h>
2. 函数功能:
C库函数void * calloc(size_t nitems,size_t size)
分配所需的内存空间,并返回一个指向它的指针。 malloc和calloc之间不同的是,malloc不会设置内存为零,而calloc会设置内存为零。
3. 函数原型:void * calloc(size_t nitems,size_t size)
-
函数名:calloc
-
形式参数:
- size_t nitems:申请多少个
- size_t size:一个占几个内存单元(一个内存单元等于一个字节)
-
返回值类型:void*:该函数返回一个指针,指向已分配大小的内存。如果请求失败,返回NULL。
-
举例:
int *p = (int*)calloc(3,4); // p指向的空间大小是12个字节 if(!p) { printf("内存申请失败!\n"); } printf("内存申请成功!\n");
-
说明:
-
calloc函数分配的内存有默认值,每个单元都是0;
-
calloc函数申请的内存空间连续;
-
calloc大多数为数组中的元素申请内存。
转存栈中数组中的数据;
int arr[3] = {10,20,30}; // 在栈区 int *p = (int*)calloc(3,4); // 申请内存,在堆区 if(!p) { puts("内存申请失败!\n"); } puts("内存申请成功!\n"); // 转存 for(int i = 0; i < 3; i++) { p[i] = arr[i]; } // 遍历 for(int i = 0; i < 3; i++) { printf("%d ",p[i]); } printf("\n"); // p使用完,记得释放内存 free(p); p = NULL; // 内存回收后,建议置空
-
(三)realloc
1. 头文件:#include <stdlib.h>
2. 函数功能:
尝试重新调整之前调用malloc或calloc所分配的p所指向的内存块的大小。
3. 函数原型:void *realloc(void *ptr,size_t size)
-
函数名:realloc
-
形式参数:
- void *ptr:是malloc或calloc的返回值
- size_t size:重新分配后的内存大小
-
返回值类型:void*:该函数返回一个指针,指向已分配大小的内存。如果请求失败,返回NULL。
-
案例:
int *p = (int*)malloc(4); int *w = (int*)realloc(p,20); // int *q = (int*)realloc(p,0); // 等效于free(p)
- 说明:
- realloc以原来malloc返回的内存地址开始,分配总共20个字节的内存空间。
- 如果原来的内存空间后有20个连续空间,就扩容20-1=16个内存单元,返回原来旧的内存首地址。
- 如果原来的内存空间后不够20个连续内存空间,就重新找一个内存地址开始,申请20个内存单元。并将原来的数据拷贝到新的内存中,回收旧的内存单元,并返回新的内存首地址。
- 说明:
(四)free
1. 头文件:#include <stdlib.h>
2. 函数功能:
释放之前调用malloc、calloc、realloc所分配的内存空间,访问完记得使用NULL置空。
3. 函数原型:void free(void *ptr)
-
函数名:free
-
形式参数:
- void *ptr:calloc、malloc、realloc的返回值
-
返回值类型:void:没有返回值
-
注意:
- 堆内存中的指针才需要回收,栈中系统会自动回收。
- 堆内存不能重复回收,运行会报错。
-
说明:
- 堆的内存空间相比较栈要大很多。
- 内存分配函数返回的指针变量可以参与运算(只读),但不能被修改(p++或者p+=i是错误的)
二、void与void*的区别
(一)定义:
- void:是空类型,是数据类型的一种
- void*:是指针类型,是指针类型的一种,可以匹配任意类型的指针,类似于通配符
(二)void
1. 说明:
void作为返回类型使用,表示没有返回值;作为形参表示形参列表为空,在调用函数时不能给实参
2. 举例:
// 函数声明
void fun(void); // 等效于 void fun();
// 函数调用
fun();
(三)void*
1. 说明:
-
void*是一个指针类型,但该指针的数据类型不明确,无法通过解引用获取内存中的数据,因为
void*
不知道访问几个内存单元。 -
void*是一种数据类型,可以作为函数返回值类型,也可以作为形参类型。
-
void*类型的变量在使用之前必须强制类型转换,明确它能够访问几个字节的内存空间。
int *p = (int*)malloc(4); double *p2 = (double*)malloc(8);
2. 举例:
#include <stdio.h>
#include <stdlib.h>
// 函数定义
void* fun(void* p) // 指针函数(返回值类型是指针的函数,此时返回的是不明确类型,需要外部强转)
{
int *p;
// double *p;
// long *p;
// char *p;
return p;
}
// 函数调用
void mian()
{
int *p;
void* a = fun(p); // 这种接收方式,实际上没有意义
printf("%p\n",a); // 可以正常打印,打印出一个地址
*a = 10; // 编译报错,void*变量不能解引用访问数据
int *w = (int*)a;
*w = 10; // 编译和运行正常,void*变量a在使用前已经强制类型转换了,数据类型明确了,访问的内存单元明确了
}
-
说明:
- void*作为返回值类型:这个函数可以返回任意类型(
char
,char*
,double*
等)的指针。 - void*作为形参类型:这个函数在调用时,可以给任意类型(
char
,char*
,double*
等)的指针。
- void*作为返回值类型:这个函数可以返回任意类型(
-
总结:
- void*类似于通配符,不能对
void*
类型的变量解引用(因为不明确内存单元的大小)。 - void*在间接访问(解引用)前要强制类型转换,但不能太随意,否则存和取的数据类型不一致。
- void*类似于通配符,不能对
三、内存操作函数
我们对于内存的操作借助于stringh
这个库提供的内存操作函数。
(一)内存填充–memset 函数
1. 头文件:#include <string.h>
2. 函数功能:
填充s开始的堆内存空间前n个字节,使得每个字节值为c。
3. 函数原型:void *memset(void *s,int c,size_t n);
-
函数参数:
- void *s:带操作内存的首地址。
- int c:填充的字节数据。
- size_n:填充的字节数。
-
返回值:返回s
-
注意:c常常设置为0,用于动态内存初始化。
-
案例:
/** * 内存操作函数-memset */ #include <stdio.h> #include <stdlib.h> #include <string.h> void test1() { // 在堆内存申请了一块存储空间 int *p = (int*)malloc(4 * sizeof(int)); if(!p) { puts("内存分配失败!"); return;// 后续代码不需要执行 } // 给这块内存进行初始化操作(填充) memset(p,0, 4 * sizeof(int)); printf("%d\n",*(p+1)); // 内存使用完毕,进行回收 free(p); p = NULL; } int main() { test1(); return 0; }
(二)内存拷贝–memcpy | memmove函数
1. 头文件:#include <string.h>
2. 函数功能:
拷贝src开始的堆内存空间前n个字节,到dest对应的内存中。
3. 函数原型:
void *memcpy(void *dest,const void *src,size_t n);
适合目标地址与源地址内存无重叠的情况。
void *memmove(void *dest,const void *src,size_t n);
-
函数参数:
- void *dest:目标内存首地址。
- void *src:源内存首地址。
- size_n:拷贝的字节数。
-
返回值:返回dest
-
注意:
- 内存申请了几个内存空间,就访问几个内存空间,否则数据不安全。
- memcpy与memmove一般情况下式一样的,更建议使用memmove进行内存拷贝;因为memmove函数是从自适应(从后往前或者从前往后)拷贝,当被拷贝的内存和目的地的内存有重叠时,数据不会出现拷贝错误;而memcpy函数是从前往后拷贝,当被拷贝的内存和目的地的内存有重叠时,数据会出现拷贝错误。
-
案例:
/** * 内存操作函数-memcpy|memmove */ #include <stdio.h> #include <stdlib.h> #include <string.h> void test1() { // 申请内存 // int *p1 = (int*)malloc(4 * sizeof(int)); // int *p2 = (int*)malloc(6 * sizeof(int)); // 给p1,p2填充数据,可以使用for循环.. // for(int i = 0; i < 4; i++) // p1[i] = 10 + i; // memset(p2,0,6 * sizeof(int)); // 创建数组 int p1[4] = {11,12,13,14}; int p2[6] = {21,22,23,24,25,26}; // 将p1中的数据通过内存拷贝函数,拷贝到p2 // memcpy(p2+2,p1+1,2*sizeof(int)) // int p2[6] = {21,22,12,13,25,26} memmove(p2+2,p1+1,2*sizeof(int)); // 测试输出数组 for(int i = 0; i < 4; i++) { printf("%4d",p1[i]); } printf("\n"); for(int j = 0; j < 6; j++) { printf("%4d",p2[j]); } printf("\n"); // 如果使用手动分配的指针,一定要记得释放内存 // free(p1); // free(p2); // p1 = NULL; // p2 = NULL; } int main() { test1(); return 0; }
(三)内存比较-- memcmp 函数
1. 头文件:#include <string.h>
2. 函数功能:
比较src和dest所代表的内存前n个字节的数据。
3. 函数原型:int memcmp(void *dest,const void *src,size_t n);
-
函数参数:
- void *dest:目标内存首地址
- const viod* src:源内存首地址
- size_t n:比较的字节数
-
返回值:
0
:数据相同>0
:dest中的数据大于src<0
:dest中的数据小于src
-
注意:n一般和src,dest的总容量一样;如果不一样,内存比较的结果就不确定 了。
-
案例:
/** * 内存操作-memcmp */ #include <stdio.h> #include <stdlib.h> #include <string.h> void test1() { // 申请内存 int *p1 = (int*)malloc(3*sizeof(int)); int *p2 = (int*)malloc(4*sizeof(int)); // int p1[4] = {1,0,3,6}; // int p2[4] = {1,2,3,4}; // int result = memcmp(p1,p2,4*sizeof(int)); *p1 = 65; *p2 = 70; char *a = (char*)p1;// 类型转换 char *b = (char*)p2; printf("%c,%c\n",*a,*b); int result = memcmp(a+1,b+1,1*sizeof(char)); printf("%d\n",result); } int main() { test1(); }
(四)内存查找–memchr | memrchr函数
1. 头文件:#include <string.h>
2. 函数功能:
在s开始的堆内存空间前n个字节中查找字节数据c
3. 函数原型:int *memchr|*memrchr(const void *s,int c,size_t n);
-
函数参数:
- const void *s:待操作内存首地址;
- int c:待查找的字节数据;
- size_t n:查找的字节数。
-
返回值:返回查找到的字节数据地址
-
注意:如果内存中没有重复数据,memchr和memrchr结果一样;如果内存中有重复数据,memchr和memrchr结果不一样。
举例:
void *memrchr(..); // 在使用时编译会报错,需要使用外部声明 // 外部申请 extern void *memechr(..);
-
案例:
/** * 内存操作-memchr | memrchr */ #include <stdio.h> #include <stdlib.h> #include <string.h> // 声明外部函数 extern void *memrchr(const void *s,int c,size_t n); void test1() { // 申请内存 int *p = (int*)malloc(4*sizeof(int)); if(!p) { puts("内存分配失败!"); return; } // 给变量赋值 for(int i = 0; i < 4; i++) { p[i] = i * 2; } p[3] = 4; // 输出 for(int i = 0; i < 4; i++) { printf("%d,",p[i]); } printf("\n"); // 内存查找 memchr int *x = (int*)memchr(p,4,4*sizeof(int)); printf("%p--%p--%d\n",x,p,*x); // 内存查找 memrchr int *y = (int*)memrchr(p,4,4*sizeof(int)); printf("%p--%p--%d\n",y,p,*y); // 回收内存 free(p); p = NULL; } int main() { test1(); }