C语言------内存管理

系列文章目录
c语言指针
项目开发常用字符串应用模型

文章目录

  • 作用域
    • 局部变量
    • 全局变量
    • extern全局变量声明
    • 静态变量
      • 静态(static)局部变量
      • 静态(static)全局变量
    • 未初始化
    • 全局函数和静态函数
      • 全局函数
      • 静态函数(内部函数)
    • 注意事项
  • 内存布局
    • 内存分区
    • 存储类型总结
    • 堆区内存分配和释放
      • 1.第一种方式(设置)
      • 2. 开辟堆空间存储数据
        • malloc
        • free
    • 堆空间存储数据
    • 内存操作函数
      • 1) memset
      • 2) memcpy
      • 3) memmove
      • 4) memcmp
  • 内存常见问题
      • 1)越界
      • 2)开辟释放0个字节的空间
      • 3)开辟内存空间能存2.5个整型
      • 4)释放之后再释放
      • 5)开辟和释放不是同一个指针
      • 6)指针 值传递
          • 解决办法1
          • 解决办法2
  • 二级指针对应的堆空间


作用域

作用域就是它能在哪去使用。生命周期就是从哪开始从哪结束,例如:夏虫朝菌、浮游朝生暮死、昙花一现······

C语言变量的作用域分为:

  1. 代码块作用域(代码块就是 { } 之间的一段代码)
  2. 函数作用域
  3. 文件作用域

局部变量

使用auto修饰,局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块 {} 内部定义的变量都是自动变量,它有如下特点:

  • 在函数内部定义的变量,只在函数范围内有效;
  • 在复合语句中定义,只在复合语句中有效;
  • 如果没有赋初值,内容为随机
      1. 局部变量未初始化,初值为随机值:C规范对该初值并没有做规定,具体实现由编译器决定。
      1. 全局变量未初始化,默认设置为初值。
      1. 局部未初始化变量,编译通过与否与具体编译环境相关。
  • 作用域:在函数内部,从变量定义到函数结束。
  • 生命周期:从变量定义到函数结束。
    • 随着函数调用的结束或复合语句的结束局部变量的生命周期也结束
  • 局部变量存在栈区。

复合语句(compound statement)简称为语句块,它使用大括号 { } 把许多语句和声明组合到一起,形成单条语句。
{ [声明和语句的列表] }

加不加auto都指的是局部变量,一般都不加auto进行修饰,跟声明extern一样不加。

#include 

void Add(int a,int b)//形参也是局部变量
{
	int c = 0;//局部变量
}

int main(void)
{
	/*
	在函数内部定义的变量  局部变量  
	作用域:在函数内部,从变量定义到函数结束
	生命周期:从变量定义到函数结束
	*/
	auto int a = 6;//定义变量  局部变量  

	for (int i = 0; i < 10; i++) {} //局部变量 i  作用域只限于for函数中

	//printf("%d\n", i);//err
	printf("%d\n", a);//6

	return 0;
}

全局变量

  • 在函数外定义,可被本文件及其它文件中的函数所共用,若其它文件中的函数调用此变量,需用 extern 声明;
    • int j; 或者 extern int j;
    • 不声明找不到
  • 作用域:整个项目中所有文件,如果在其他文件中使用,需要声明,但不能重复定义,不同文件的全局变量不能重复。
  • 全局变量的生命周期和程序运行周期一样;
    • 生命周期:从程序创建到程序销毁。
  • 全局变量存的不是栈区,存在数据区。
  • 不同文件的全局变量不可重名
    • 局部变量可以和全局变量同名,不冲突,局部变量在栈区,是两个不同的变量,在操作的时候,数据是采取就近原则。

C语言------内存管理_第1张图片

test1.c

#include 
//全局变量  在函数外部定义的变量
//作用域:整个项目中所有文件 如果在其他文件中使用  需要声明
//生命周期:从程序创建到程序销毁
int j = 1;

void Func02() 
{
	j = 666;
	printf("%d\n", j);
}
int main() 
{
	printf("%d\n", j);//1
	Func02();//666
	Func03();//666
	return 0;
}

test2.c

#include 

extern int j;//声明
void Func03() 
{
	printf("%d\n", j);
}

C语言------内存管理_第2张图片

如果不声明会报错,只有声明了编译才能通过。
如果不声明会报错

#include 
int j = 1;

int main() 
{
	printf("%d\n", j);//1
	int j = 60;
	printf("%d\n", j);//60

	//匿名内部函数
	{	//这里叫代码体(程序体)
		//int j = 52;//第一种 ------------------------------
		j = 52;//第二种 ------------------------------
		printf("%d\n", j);//52
	}	//程序结束之后这个函数就销毁了

	printf("%d\n", j);//第一种:60 ; 第二种:52

	return 0;
}

还可以打印地址看看printf("%p\n", &j);

extern全局变量声明

extern int a; 声明一个变量,这个全局变量在别的文件中已经定义了,这里只是声明,而不是定义。

静态变量

存储的位置:数据区

静态(static)局部变量

  • static局部变量的作用域也是在定义的函数内有效,只能在函数内部使用
  • static局部变量的生命周期和程序运行周期一样,从程序创建到程序销毁,同时staitc局部变量的值只初始化一次,但可以赋值多次。
  • 局部变量要优于函数,静态局部变量在数据区进行存储,程序在执行起来的时候,static这句话就已经走完一遍了
  • static局部变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋空字符
#include

void Func01() 
{	
	//int y = 1;//局部变量
	static int y = 1;//静态局部变量 
	y++;
	printf("%d\n", y);
}

int main()
{
	//静态局部变量
	//static int y = 1;
	//printf("%d\n", y);
	
	for (int i = 0; i < 10; i++)
	{
		Func01();
	}

	return 0;
}

静态(static)全局变量

作用域:可以在本文件中使用,但是不可以在其他文件中使用

生命周期:从程序创建到程序销毁。

#include 
//静态全局变量  
static int c = 1;
void Func() 
{
	c = 666;
	printf("%d\n", c);
}

int main() 
{
	for (int i = 0; i < 10; i++)
	{
		c++;
		printf("%d\n", c);
	}
	Func();
}

C语言------内存管理_第3张图片
不可以在其他文件中使用静态全局变量,不然会报错
C语言------内存管理_第4张图片

未初始化

局部变量未初始化在Visual Studio中是不允许使用的,因为虽然内存开辟空间了,但是打印的值都是乱码(任意值)。在其他的编译环境中是允许使用的,Visual Studio做一个安全限制,你没有初始化它一定是错的,它会给你进行报错。
C语言------内存管理_第5张图片
未初始化的全局变量:0
C语言------内存管理_第6张图片
未初始化的静态全局变量:0
C语言------内存管理_第7张图片
未初始化的静态局部变量:0
C语言------内存管理_第8张图片

全局函数和静态函数

全局函数

在C语言中函数默认都是全局的(全局函数),extern可省略。项目中的所有文件都可以去调用它。

声明函数
extern 类型标识符 函数名( 形参列表 );
或者
类型标识符 函数名( 形参列表 );

C语言------内存管理_第9张图片

其实不声明程序能找到,但是如果你声明了你就可以右键跳转,找起来比较方便,声明是有一定意义的,声明一般是放在头文件中( .h文件),建议声明

C++有多态可以方法重载;C语言不支持一个函数名对应多个参数的样式,所以全局函数名称是作用域中唯一的。

作用域: 在整个项目中所有文件中使用。
生命周期:从程序创建到程序销毁

静态函数(内部函数)

在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静
态,函数定义为static就意味着这个函数只能在定义这个函数的文件中使
用,在其他文件中不能调用,即使在其他文件中声明这个函数都没用。

对于不同文件中的staitc函数名字可以相同。

static 类型标识符 函数名( 形参列表 );

作用域:当前文件中

静态函数可以和全局函数重名,当前文件中优先调用静态函数,就近原则。当静态函数和全局函数重名并且在同一文件中时,报错。

生命周期:从程序创建到程序销毁

#include 

static void FuncStatic()
{
	printf("静态函数\n");
}

int main() 
{
	FuncStatic();
	return 0;
}

C语言------内存管理_第10张图片

注意事项

C语言------内存管理_第11张图片

  • 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。
  • 同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用。(就近原则)
  • 所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是staitc函数,那么作用域是文件级的,所以不同的文件static函数名是可以相同的。

内存布局

内存分区

内存不止四区,内存四区只是对于我们应用程序来说的,内存四区模型图:
C语言------内存管理_第12张图片

全局常量,安全的常量 ,存储区域为数据区常量区。局部常量是不安全的。

  • 代码区:程序执行的二进制码(程序指令)。存放CPU执行的机器指令。另外,代码区还规划了局部变量的相关信息。
    • 特点
      1. 共享
        通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
      2. 只读
        代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。
  • 数据区:加载的信息都是跟程序同生共死的
    • 全局初始化数据区/静态数据区(data段 或者 data segment
      该区包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。
    • 未初始化数据区(又叫bss区
      存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为0或者空(NULL)。
    • 常量区
  • 栈区(stack):系统为每一个程序分配一个临时的空间
    • 一般存局部变量、函数信息、函数参数、数组、指针、结构体。
    • 栈区大小为1M,在windows中可以扩展到10M、在Linux中可以扩展到16M
    • 栈是一种先进后出的内存结构,由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。
    • 存储变量的时候是从高地址开始存向下增长(只有栈区是这样的),数组是例外首元素地址是从低地址开始。
    • 函数入栈是从后向前入栈,例如函数 void func(int a,int b){} ,先存b再存a。
    • 入栈是从高地址到低地址,出栈是从低地址到高地址,先进后出、后进先出(沙桶原理)
  • 堆区(heap)
    • 公共的区域
    • 大小:理论是没有任何限制的,跟内存有关,除了上面三个区域剩下的全都是堆区,内存越大堆区越大,没有栈那样先进后出的顺序。用于动态内存分配。
    • 存储大数据、图片、音频文件
    • 需要手动开辟内存空间,malloc、colloc、realloc,是一块连续的空间,这个连续的空间会获取一个指针,这里面没有变量名,只能通过指针来操作。
    • 需要手动释放,free
    • 一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。
    • 从低地址开始存,向上增长

存储类型总结

#include 

//全局常量 安全的常量 存储区域为数据区常量区
const int c1 = 123;
//未初始化的全局变量
int x1;
//初始化全局变量
int x2 = 52;
//未初始化的静态全局变量
static int s1;
//初始化静态全局变量
static int s2 = 16;

int main() 
{
	const int c2 = 123;//局部常量 不安全的

	int y = 10;

	//未初始化的局部变量
	static int j1;
	//初始化的局部变量
	static int j2 = 10;

	char* p = "hello xy";//字符串常量

	int arr[] = { 1,2,3,4 };//数组

	int** pp = arr;//指针

	printf("未初始化的全局变量: % p\n", &x1);
	printf("未初始化的静态全局变量: % p\n", &x2);
	printf("未初始化的静态全局变量: % p\n", &s1);
	printf("初始化静态全局变量: % p\n", &s2);
	printf("局部变量: % p\n", &y);
	printf("字符串常量: % p\n", p);
	printf("数组: % p\n", &arr);
	printf("指针变量: % p\n", pp);
	printf("指针地址: % p\n", &pp);

	return 0;
}

C语言------内存管理_第13张图片
可以看到红色的存储区域都是数据区,里面又有一些区别,初始化的、未初始化、字符串常量。

绿色的是栈区,栈区一般存局部变量、数组信息、指针、结构体。

堆区内存分配和释放

栈区超出1M会报错,我们定义一个数组看看效果

#include 

int main()
{
	int arr[1000000] = { 0 };//超出1M(默认),报错
	/*
		字节B 1000000*4=4000000
		KB 4,000,000/1024=3,906.25
		MB 3,906.25/1024=3.81
	*/

	//int arr[1000000/4] = { 0 };//没有超出
	return 0;
}

C语言------内存管理_第14张图片

1.第一种方式(设置)

默认是1M,如果想要扩充,通过程序编译之前,右键项目选择属性,windows中最大可改为10M,默认单位为byte,修改为10M,1010241024=10485760

#include 
int main()
{
	//栈区大小
	//int arr[1000000] = { 0 };//超出1M,报错
	/*
		字节B 1000000*4=4000000
		KB 4,000,000/1024=3,906.25
		MB 3,906.25/1024=3.81
	*/

	int arr[1000000/4] = { 0 };//没有超出
	
	return 0;
}

C语言------内存管理_第15张图片
C语言------内存管理_第16张图片

C语言------内存管理_第17张图片

2. 开辟堆空间存储数据

malloc

#include
void* malloc(size_t size);

功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。
参数:

  • size:需要分配内存大小(单位: 字节)

返回值:

  • 成功:分配空间的起始地址,
  • 失败:NULL

free

#include

void free(void* ptr);

功能: 释放ptr指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址,对同一内存空间多次释放会出错。
参数:
ptr:需要择放空间的首地址,被释放区应是由malloc函数所分的区域。

返回值:

注意:开辟和释放必须是同一个指针

就算没有free(),main()结束后也是会自动释放malloc()的内存的,free()的用处在于实时回收内存。如果你的程序很简单,那么你不写free()也没关系。

#include 
int main()
{
	//开辟堆空间存储数据
	int* p = (int*)malloc(sizeof(int));

	printf("%p\n",p);//01685FE0
	printf("%d\n",*p);//-842150451

	//使用堆空间
	*p = 123;
	printf("%d\n", *p);//123

	//释放堆空间
	free(p);

	printf("释放完之后:%p\n", p);//01685FE0
	printf("释放完之后:%d\n", *p);//-572662307
	*p = 456;
	printf("%d\n", *p);//456

	return 0;
}

释放完还能操作这个堆空间地址吗?答案是可以,释放完p之后这个p就变成野指针了,p指向未知空间了,操作野指针对应的空间是可能报错的,可能也不报错的,我们尽量避免野指针的出现,可以在每次使用完p之后p=NULL;把p置为空。

注意:开辟堆空间的时候,要有连续的空间,如果没有就失败了

#include 
#include 
int main(void)
{
	//int* p = (int*)malloc(sizeof(int) * 1000000);//3M   打印的地址:00EE4040
	//int* p = (int*)malloc(sizeof(int) * 1000000 * 100);//300M	打印的地址:00EE4040
	//int* p = (int*)malloc(sizeof(int) * 1000000 * 1000);//3000M==2.92GB 打印的地址:00000000
	int* p = (int*)malloc(sizeof(int) * 1000000 * 1000 / 3);//1000M==1GB 打印的地址:01288040
	
	printf("%p\n",p);//

	free(p);
	return 0;
}

一般情况下, 加载到内存中的数据最多控制在1个G左右。

堆空间存储数据

#include 
#include 
int main(void)
{
	//开辟10个内存大小
	int* p = malloc(sizeof(int) * 10);//开辟了40字节

	//一般不写
	//if (!p) {//p == NULL
	//	printf("程序异常\n");
	//	return -1;
	//}

	for (int i = 0; i < 10; i++)
	{
		p[i] = i;
	}

	for (int i = 0; i < 10; i++)
	{
		//printf("%d\n", p[i]);
		printf("%d\n", *(p+i));
	}

	free(p);
	return 0;
}

还可以放随机数

#include 
#include 
#include 
//随机数导入stdlib、time.h文件
#define MAX 10
int main()
{
	srand((size_t)time(NULL));

	int* p = (int*)malloc(sizeof(int) * MAX);

	for (int i = 0; i < MAX ; i++)
	{
		p[i] = rand() % 100;
		printf("%d\n",p[i]);
	}
	free(p);
	return 0;
}

内存操作函数

1) memset

#include
void * memset(void *s, int c, size_t n);

功能: 将的内存区域的前n个字节以参数填入,栈区堆区都可以
参数:

  • s:需要剩作内存s的首地址
  • c: 填的疗符.c虽然参数为int,但必须是unsigned char,范围为0~255
  • n:指定需要设置的大小

返回值: s的首地址

#include 
#include 
#include 

int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);//40

	//memset()重置内存空间的值
	//memset(p,0,40);//打印全是0

	memset(p,1,40);//打印全是16843009
	

	for (int i = 0; i < 10; i++)
	{
		printf("%d\n",p[i]);
	}

	free(p);
	return 0;
}

C语言------内存管理_第18张图片

一般都是用 memset(p,0,40); 重置成0。

#include 
#include 
#include 

int main()
{
	char ch[10];//栈区
	memset(ch,'y',10);

	for (int i = 0; i < 10; i++)
	{
		printf("%c\n",ch[i]);
	}

	return 0;
}

2) memcpy

#include
void * memcpy(void *dest, const void * src , size_t n);

功能: 拷贝src所指的内存内容的前n个字节到dest所值的内存地址上。
参数:

  • dest:目的内存首地址
  • src: 源内存首地址,注意:dest和src所指的内存空间不可重叠,可能会导致程序报错,内存里面一边往里读一边往里写,可能会导致内存被占用掉
  • n:需要拷贝的字节数

返回值: dest的首地址

#include 
#include 
#include 

int main()
{
	int arr[10] = { 0,9,1,2,3,4,5,6,7,8 };
	int* p = (int*)malloc(sizeof(int)*10);

	memcpy(p, arr, sizeof(int) * 10);

	for (int i = 0; i < 10; i++)
	{
		printf("%d\n",p[i]);
	}

	free(p);
	return 0;
}

字符串拷贝strcpy 和 内存拷贝memcpy 的区别

  • strcpy 字符串拷贝遇到\0停止,只会将第一个\0之前的内容拷贝过来。
  • 内存拷贝memcpy 不管你是不是\0都拷贝,拷贝的内容和字节有关,和内容无关。
#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 

int main()
{
	char ch[] = "hello\0 xy";
	char s[100];

	//strcpy(s,ch);//字符串拷贝遇到\0就停止
	memcpy(s,ch,9);

	for (int i = 0; i < 9; i++)
	{
		printf("%c",s[i]);
	}
	
	return 0;
}

当拷贝源和拷贝目标发生重叠

#include 
#include 

int main()
{
	int arr[] = {0,1,2,3,4,5,6,7,8,9};

	memcpy(&arr[5], &arr[3], 12);//0 1 2 3 4 3 4 5 8 9

	for (int i = 0; i < 9; i++)
	{
		printf("%d ",arr[i]);
	}
	
	return 0;
}

0,1,2,3,4,5,6,7,8,9         ==>        0 1 2 3 4 3 4 5 8 9

3) memmove

memmove() 功能用法和 memcpy() 一样,区别在于:dest和src所指的内存空间重叠时,memmove() 仍然能处理,不过执行效率比memcpy() 低些。

如果源和目标重叠,它会自己在内存中开辟一块空间,先把你 源 挪到这去,然后再挪到目标文件,这样就不会报错了。

不重叠的时候跟memcpy一样

#include 
#include 

int main()
{
	int arr[] = { 0,1,2,3,4,5,6,7,8,9 };

	//memcpy(&arr[5], &arr[3], 12);
	memmove(&arr[5], &arr[3], 12);//0 1 2 3 4 3 4 5 8

	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

4) memcmp

#include
void * memcmp(const void *s1, const void *s2, size_t n);

功能: 比较s1和s2所指向内存区域的前n个字节,比较的是内存中的值,不限于字符串、整型······
参数:

  • s1:内存首地址s1
  • s2:内存首地址s2
  • n:需比较的前n个字节

返回值:

  • 相等:=0
  • 大于:>0
  • 小于:<0
#include 
#include 

int main()
{
	int arr1[] = { 0,1,2,3,4,5,6,7,8,9 };
	int arr2[] = { 0,1,2,3,4,5 };

	int val= memcmp(arr1,arr2,20);
	printf("%d\n", val);//0
	return 0;
}

比较字符串:strcmp(s1,s2)只能比较 \0 之前的内容;memcmp可以比较\0之后,它比较的跟字节有关。

#include 
#include 

int main()
{
	char s1[] = "hello\0 xy";
	char s2[] = "hello\0 xy";

	int val= memcmp(s1,s2,10);
	printf("%d\n", val);//0
	return 0;
}

内存常见问题

1)越界

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
int main()
{
	//数组下标越界
	//char ch[8] = "hello xy";

	//堆空间数组下标越界
	char* p = (char*)malloc(sizeof(char) * 8);
	strcpy(p,"hello xy");

	printf("%s\n",p);

	/* 我开辟了8空间,但是我用了9个空间,
	释放是按照8释放还是9释放?按8释放没释放掉,按9释放释放多了
	释放多余的空间会出现问题 */

	//free(p);//一用就报错
	return 0;
}

C语言------内存管理_第19张图片

2)开辟释放0个字节的空间

C语言------内存管理_第20张图片

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 
#include 
int main()
{
	
	int* p = (int*)malloc(0);//野指针
	
	printf("%p\n", p);

	*p = 666;
	printf("%d\n", *p);

	//free(p);//一用就报错
	return 0;
}

C语言------内存管理_第21张图片

3)开辟内存空间能存2.5个整型

一般情况建议用int* p = (int*)malloc(sizeof(int)*10);

#include 
#include 
int main()
{
	int* p = (int*)malloc(10);
	
	p[0] = 0;
	p[1] = 11;
	//p[2] = 22;//一用就报错

	printf("%p\n", p);
	printf("%d\n", *p);
	printf("%d\n", *(p+1));

	free(p);

	return 0;
}

C语言------内存管理_第22张图片

4)释放之后再释放

第一次释放是正确指针,第二次释放是野指针,释放野指针是不对的,操作野指针空间可能报错,释放野指针一定报错。堆空间不允许多次释放
C语言------内存管理_第23张图片

我们用完给它置为空指针,就不会报错啦,堆空间不允许多次释放,空指针允许多次释放

#include 
#include 
int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	
	free(p);
	p = NULL;//空指针
	free(p);
	free(p);
	free(p);
	free(p);
	free(p);

	return 0;
}

5)开辟和释放不是同一个指针

通过指针操作对应的堆空间的时候,尽量不要修改,如果需要修改最好做一个备份。

#include 
#include 
int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	printf("%p\n",p);
	for (int i = 0; i < 10; i++)
	{
		//指针叠加 不断改变指针方向,释放会出错
		*p++ = i;
	}
	printf("%p\n", p);
	free(p);//这时候的p地址不是一开始的地址了

	return 0;
}

C语言------内存管理_第24张图片

正确:备份

int main()
{
	int* p = (int*)malloc(sizeof(int) * 10);
	//printf("%p\n",p);
	int* temp = p;
	for (int i = 0; i < 10; i++)
	{
		//指针叠加 不断改变指针方向,释放会出错
		//*p++ = i;
		*temp++ = i;
	}
	//printf("%p\n", p);
	free(p);//这时候的p地址不是一开始的地址了

	return 0;
}

6)指针 值传递

因为这两个p都是一级指针(同一级别的),虽然两个都是指针,但是指针传递的情况下,这种方式也是叫值传递。这种方式并没有堆空间地址传递过来。

#include 
#include 

void Func01(int * p) 
{
	p = (int*)malloc(sizeof(int) * 10);
}

int main()
{
	int* p = NULL;
	Func01(p);

	for (int i = 0; i < 10; i++)
		p[i] = i;

	free(p);
	return 0;
}

C语言------内存管理_第25张图片
C语言------内存管理_第26张图片

我们想要地址传递应该这样写:

解决办法1

用更高级的指针接收它

#include 
#include 

void Func01(int** p) //
{
	*p = (int*)malloc(sizeof(int) * 10);//
}

int main()
{
	int* p = NULL;
	Func01(&p);//

	for (int i = 0; i < 10; i++)
		p[i] = i;

	free(p);
	return 0;
}

C语言------内存管理_第27张图片
打印地址看看
C语言------内存管理_第28张图片

解决办法2

返回值去接收

#include 
#include 

int* Func03() 
{
	int* p = malloc(sizeof(int) * 10);
	return p;
}

int main()
{
	int* p = NULL;
	p = Func03(&p);

	for (int i = 0; i < 10; i++)
		p[i] = i;

	for (int i = 0; i < 10; i++)
		printf("%d ",p[i]);

	free(p);
	return 0;
}

打印0 1 2 3 4 5 6 7 8 9。因为堆空间在函数Func03结束之后不会被释放,所以p操作堆空间不会报错。

二级指针对应的堆空间

#define _CRT_SECURE_NO_WARNINGS
#include 
#include 

int main()
{
	//int p[5][3];
	//开辟二级指针对应的堆空间
	int** p = (int**)malloc(sizeof(int*) * 5);

	//开辟5行3列的二维数组
	for (int i = 0; i < 5; i++)
	{
		//开辟一级指针对应的堆空间
		p[i] = (int*)malloc(sizeof(int) * 3);
	}
	//存值
	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			scanf("%d", &p[i][j]);
		}
	}
	//取值
	for (int i = 0; i < 5; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d ", p[i][j]);
		}
		puts("");
	}
	//先释放里面的再释放外面的
	for (int i = 0; i < 5; i++)
	{
			free(p[i]);
	}
	free(p);

	return 0;
}

C语言------内存管理_第29张图片

注意:不是连续的堆空间

输入
1 2 3
4 5 6
7 8 9
10 11 12
13 14 15
C语言------内存管理_第30张图片

你可能感兴趣的:(C,c语言,开发语言)