正文开始@马上回来了
我们已经掌握的内存开辟方式有:
int a =20;//在栈空间开辟四个字节
int arr[10] = {0};//在栈空间上开辟了连续40个字节的空间
以上两种开辟空间的方式有两个特点:
但是对于空间的需求不仅仅是以上两种情况,有时候我们需要空间的大小在程序运行的时候才知道,那数组在编译的时候开辟空间的方式就不能满足了。
这时候就只能试试动态内存开辟了。
C语言提供了一个动态内存开辟的函数malloc,函数原型如下:void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并且返回指向这块空间的指针。
C语言提供了另外一个函数free,专门用来做动态内存释放和回收的,函数原型如下:void free (void* ptr);
free函数用来释放动态开辟的内存。(至于为什么动态开辟的内存需要释放,后面会详细讲解。)
malloc和free函数都声明在stdlib.h
头文件中。
下面看看这两个函数的简单实用:
#define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
int main()
{
int num = 0;
scanf("%d", &num);//输入你想要开辟空间的大小,单位是字节
int * p = (int*)malloc(sizeof(int)*num);
//INT_MAX 最大范围,超过了这个值无法开辟空间成功
if (p == NULL)//判空
{
perror("malloc");//打印错误信息
return 1;//如 如果没有开辟成功,到这里程序就结束了
}
//使用
int ptr = p;//用ptr记录malloc返回的起始地址
int i = 0;
for (i = 0; i < num; i++)//循环遍历malloc开辟的连续空间
{
*p = i;//赋值
printf("%d ", *p++);//打印
}
//free 释放
free(ptr);
//置空,防止野指针非法访问
ptr = NULL;
return 0;
}
int main()
{
int num = 0;
scanf("%d", &num);//输入你想要开辟空间的大小,单位是字节
int* p = (int*)malloc(sizeof(int) * num);
//INT_MAX 最大范围,超过了这个值无法开辟空间成功
if (p == NULL)//判空
{
perror("malloc");//打印错误信息
return 1;//如 如果没有开辟成功,到这里程序就结束了
}
int i = 0;
for (i = 0; i < num; i++)//循环遍历malloc开辟的连续空间
{
*(p + i) = i;
printf("%d ", *(p + i));
}
//free 释放
free(p);
//置空,防止野指针非法访问
p = NULL;
return 0;
}
你可能会疑惑:
因为malloc不一定能够每次都能帮我们成功的开辟出一块连续的空间,比如说当你的参数是INT_MAX时,这时 已经超出了int类型最大值的范围,因此malloc函数会开辟空间失败,我们用perror打印出错误信息然后用 return结束。
我们调用malloc函数来帮我们开辟了一块连续的空间,我们把这块空间用完之后,如果程序运行结束了,动 态申请的内存由操作系统自动回收,如果程序运行不结束,操作系统是不会帮我们回收的,这样这块空间就 一直被占用会造成空间泄漏的问题。
因为malloc开辟的空间被free释放之后,这块空间就不在属于我们使用,指针p(ptr)如果再去访问这块空间就属于野指针去访问非法空间的问题了。
在于对malloc返回的地址的使用不同。代码1是通过改变malloc返回的指针自身来访问这块连续的空间,因此在free释放前需要一个变量来记录改变前malloc返回的起始地址。代码2则是通过指针+整数与解引用的运算相结合来访问这块连续的空malloc返回的起始地址并未发生改变。
C语言还提供了一个函数叫calloc,calloc函数也用动态内存分配。函数原型如下:void* calloc (size_t num, size_t size);
举个例子:
int main()
{
int * p =(int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
所以如果我们要对申请的空间要求初始化,那么可以很方便的使用calloc函数来完成。
函数原型如下:void* realloc (void* ptr, size_t size);
情况1:原有空间之后有足够大的空间
当是情况1的时候,要扩展内存就直接在原有内存之后直接追加空间,原来空间的数据不发生变化。
情况2:原有空间后面没有足够大的空间
当是情况2的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上找另一个合适大小的连续空间来使用。这样函数返回的就是一个新的内存地址。
由于上述两种情况,realloc函数的使用就要注意一些。要避免realloc空间开辟失败又将原来malloc返回的地址弄丢的情况。
举个例子:
int main()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");
return 1;
}
//p = (int*)realloc(p, 80);
//这样写的话,如果realloc开辟空间失败会返回NULL,p原本指向的malloc空间返回的起始地址也会被弄丢
int *ptr = NULL;//创建指针ptr,并将其置空
ptr=(int*)realloc(p, 80);
if (ptr != NULL)//判空
{
p = ptr;
}
//使用
int i = 0;
for (i = 0; i < 20; i++)
{
*(p + i) = i;
printf("%d ", *(p + i));
}
//释放
free(p);
p = NULL;
return 0;
}
int main()
{
int* p = (int*)malloc(INT_MAX);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
p = NULL;
return 0;
}
int main()
{
int* p = (int*)malloc(INT_MAX);
if (p == NULL)
{
perror("malloc");
return 1;
}
*p = 20;//如果p的值是NULL,就会有问题
free(p);
p = NULL;
return 0;
}
int main()
{
int* p = (int*)malloc(10 * sizeof(int));//只开辟了10个int类型的空间
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 11; i++)//循环遍历了11个空间,i=10的时候是越界访问
{
*(p + i) = i;
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
更正对内存布置进行检查:
int main()
{
int* p = (int*)malloc(10 * sizeof(int));//只开辟了10个int类型的空间
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)//循环遍历了10个空间
{
*(p + i) = i;
printf("%d ", *(p + i));
}
free(p);
p = NULL;
return 0;
}
int main()
{
int a = 10;//a 是局部变量
int* p = &a;
free(p);
return 0;
}
局部变量是在栈空间开辟的,程序调用结束时由编译器自动销毁。
void test()
{
int* p = (int*)malloc(100);
p++;
free(p);//p不在指向动态内存的起始地址
}
int main()
{
test();
return 0;
}
更正:如果要通过更改内存空间的起始地址来访问内存开辟的空间,那么应该提前使用一个的变量来记录内存空间起始位置的地址
int main()
{
int num = 0;
scanf("%d", &num);//输入你想要开辟空间的大小,单位是字节
int * p = (int*)malloc(sizeof(int)*num);
//INT_MAX 最大范围,超过了这个值无法开辟空间成功
if (p == NULL)//判空
{
perror("malloc");//打印错误信息
return 1;//如 如果没有开辟成功,到这里程序就结束了
}
//使用
int ptr = p;//用ptr记录malloc返回的起始地址
int i = 0;
for (i = 0; i < num; i++)//循环遍历malloc开辟的连续空间
{
*p = i;//赋值
printf("%d ", *p++);//打印
}
//free 释放
free(ptr);
//置空,防止野指针非法访问
ptr = NULL;
return 0;
}
void test()
{
int* p = (int*)malloc(100);
free(p);
free(p);//重复释放
}
void test()
{
int* p = (int*)malloc(100);
if (p == NULL)
{
perror("malloc");
return 1;
}
*p = 20;//未使用free释放动态开辟的空间
}
int main()
{
test();
while (1);//程序死循环
return 0;
}
切记:
动态开辟的空间一定要释放,并且正确释放。
void test()
{
int* p = (int*)malloc(100);
if (p == NULL)
{
perror("malloc");
return 1;
}
*p = 20;
//free释放
free(p);
p = NULL;
}
int main()
{
test();
while (1);//程序死循环
return 0;
}
函数传参无法正常返回地址问题:
void Getmemory(char* p)//形参是实参的一份临时拷贝 p是指针变量 p指向NULL *p是NULL
{
p = (char*)malloc(100);//p由指向NULL 变为指向malloc开辟空间的起始地址
}
void Test(void)
{
char* str = NULL;
GetMemory(str);//传的是实参
strcpy(str, "helllo world");
printf(str);
}
//程序会崩溃无法打印
更正:通过传实参的地址来找到实参在内存当中的位置然后再改变实参的值:
void GetMemory(char** p)//char**p接收str的地址,是一个二级指针
{
*p = (char*)malloc(100);//*p==str 由指向NULL 变为指向malloc开辟空间的起始地址
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);//转str的地址
strcpy(str, "helllo world");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
//程序会正常打印"hello world"
返回栈空间问题:
char* GetMemory(void)
{
char p [] = "hello world";//栈空间开辟数组
return p;//p放在寄存器当中,这个函数可以成功返回数组名p,也就是数组的首元素地址
}
void Test(void)
{
char* str = NULL;
str = GetMemory();
printf(str);
}
int main()
{
Test();
return 0;
}
//栈空间在用完之后会被系统回收,因此创建的数组p在GetMemory调用完之后就已经被销毁无法使用了
//str属于是野指针去访问了一块被销毁的空间,因此会打印一些随机值
int* test()
{
int a = 10;
return &a;//a的地址被存放在寄存器当中
}
int main()
{
int* p = test();//p接收到a的地址
printf("%d\n", *p);//p已经是野指针 打印10 因为调用test的栈空间还没有被覆盖
printf("%d\n", *p);// 打印随机值 有一次调用test函数,栈空间被新随机值给覆盖
return 0;
}
void GetMemory(char** p, int num)
{
assert(*p);
*p = (char*)malloc(num);
}
void tset(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
//在使用完后没有free
}
int main()
{
test();
return 0;
}
更正:
void GetMemory(char** p, int num)
{
assert(*p);
*p = (char*)malloc(num);
}
void tset(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
//free释放
free(str);
str = NULL;
}
int main()
{
test();
return 0;
}
void test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);//free释放
if (str != NULL)
{
strcpy(str, "world");//str指向的空间已经被释放,str成了野指针非法访问
printf(str);//打印出world 但这个代码是错误的
}
}
int main()
{
test();
return 0;
}
更正:
void test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello");
free(str);//free释放
str = NULL;//置空
if (str != NULL)
{
strcpy(str, "world");
printf(str);//无法打印
}
}
int main()
{
test();
return 0;
}
有了这幅图,我们就可以更好的理解在《C语言初识》中讲的static关键字修饰局部变量的例子了。
实际上局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
但是被static修饰的变量存在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁,所以生命周期变长。
也许你重来没有听说过**_柔性数组 _** (flexible array)这个概念,但是它确实是存在的。
c99中,结构中的最后一个元素允许是未知大小的数组,这就叫【柔性数组】成员。
struct s1
{
int i ;
int arr[];//柔性数组成员,未指定大小
};
struct s2
{
int i;
int arr[0];//柔性数组成员,这里的0未指定大小
};
typedef struct s
{
int i;
int arr[];//柔性数组成员,未指定大小
}s;
int main()
{
printf("%d ", sizeof(s));//4
return 0;
}
typedef struct s
{
int num;
int arr[];//柔性数组
}s;
int main()
{
s* p = (s*)malloc(sizeof(s) + 10 * sizeof(int));//malloc开辟空间的大小要大于结构体的大小
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
p->arr[i] = i;//->成员访问操作符
printf("%d ", p->arr[i]);
}
return 0;
}
typedef struct s
{
int num;
int arr[];//柔性数组
}s;
int main()
{
s* p = (s*)malloc(sizeof(s) + 10 * sizeof(int));//malloc开辟空间的大小要大于结构体的大小
if (p == NULL)
{
perror("malloc");
return 1;
}
//realloc扩容
s* ptr = NULL;
ptr = (s*)realloc(p, sizeof(s) + 20 * sizeof(int));
if (ptr != NULL)
{
p = ptr;
}
int i = 0;
for (i = 0; i < 20; i++)
{
p->arr[i] = i;//->成员访问操作符
printf("%d ", p->arr[i]);
}
//free释放
free(p);
p = NULL;
return 0;
}
typedef struct s
{
int i;
int* arr;
}s;
int main()
{
s* p = (s*)malloc(sizeof(s));//第一次malloc开辟一个结构体类型的空间
if (p == NULL)
{
perror("malloc");
return 1;
}
p->arr = (int*)malloc(10 * sizeof(int));//找到arr然后又使用malloc开辟一块空间
if (p->arr == NULL)//arr所指向的地址开辟空间失败
{
perror("malloc");
free(p);//将第一次malloc开辟的起始地址置空
p = NULL;
return 2;
}
//使用 ......0
//释放
free(p->arr);//先找到arr开辟的地址
p->arr = NULL;
free(p);//在找到p开辟的地址
p = NULL;
return 0;
}