内存的管理(c语言)

目录

前提:

一、变量的存储类别

1.1 内存的分区

①、内存:物理内存、虚拟内存

②、在运行程序时,操作系统会将虚拟内存进行分区

二、 在每块区域中变量申请空间的特点

2.1 普通的全局变量

 2.2 静态全局变量static

 2.3 普通的局部变量

2.4 静态局部变量

三、内部函数和外部函数

3.1 内部函数

3.2 外部函数

3.3 内部函数与外部函数的区别

3.4 扩展

四、内存组织方式(参考)

4.1 堆与栈

🍖堆:存放动态分配和释放内存块的存储空间

🍖栈

五、动态内存申请

5.1 动态分配内存的概述

5.2 静态分配、动态分配

①、静态分配

②、动态分配

5.3 动态分配函数

①、malloc():

②、calloc():

③、realloc():(重新申请内存)

④、free()——释放内存函数:

六、内存泄露

6.1 内存泄漏案例

内存泄漏案例1:

内存泄漏案例2:

6.2 解决方式

解决方式1

 解决方式2:




前提:

  程序在运行时,将需要的数据都组织存放在内存空间,以备程序使用。在软件开发过程中,常需要动态地分配和撤销内存空间,这就需要对内存进行管理。

一、变量的存储类别

1.1 内存的分区

①、内存:物理内存、虚拟内存

物理内存:实实在在存在的内存设备

虚拟内存:操作系统虚拟出来的内存。还是会和物理内存做练习。

操作系统会在物理内存和虚拟内存之间做映射。实际上,虚拟内存占用多少空间,都会映射到物理内存当中。

在32位系统下,每个进程的寻址地址范围是4G, 0x00 00 00 00 - 0xff ff ff ff

在32位操作系统中,虚拟内存被分为两个部分,3G的用户空间和1G内核空间,

其中用户空间:是当前进程所私有的,

内核空间:是一个系统中所有进程所公有的。

在写应用程序时,看见的都是虚拟地址。

②、在运行程序时,操作系统会将虚拟内存进行分区

1)堆

在动态申请内存时,在堆里开辟内存

2)栈

主要存放局部变量

3)静态全局区

         1:未初始化静态全局区

                静态变量(定义变量时,前面添加static修饰),或者全局变量,没有初始化,存在此区

           2:初始化的静态全局区

                全局变量、静态变量、赋过初值的,存放在辞去

4) 代码区

        存放程序代码。

5) 文字常量区

 存放常量的

二、 在每块区域中变量申请空间的特点

2.1 普通的全局变量

全局变量: 在函数外部定义的变量

作用范围

  • 程序的所有地方
  • 不同.c文件用之前需要声明,extern int num;
  • 声明时不要赋值;

生命周期:程序运行的整个过程,一直存在,直到程序结束。

#include <stdio.h> 

//定义普通全局变量
//主要在mian,子函数外就是全局变量 
//如果全局 变量没有进行初始化,则系统自动将其初始化为0 
int num; 

//全局变量可以在程序的任意位置 对其进行操作 
void fun()
{
	num = 888;	
 } 

void main()
{
	printf("%d\n", num);
    fun();
    printf("%d\n", num);
}

 2.2 静态全局变量static

定义全局变量,在定义前加static

作用范围

  • static限定静态全局变量的作用范围
  • 只能在它定义的 .c 文件中有效

 生命周期:程序运行的整个过程,一直存在。

#include <stdio.h> 

//定义静态全局变量
//静态全局变量只能在当前 .c文件中使用 
static int num; 

void fun()
{
	num++;	
 } 

void main()
{
	printf("%d\n", num);
    fun();
    printf("%d\n", num);
}

 2.3 普通的局部变量

在函数内部定义,或复合语句中定义的变量

作用范围

  • 在函数内部定义,在函数中有效
  • 在复合语句定义,在复合语句有效

生命周期

  • 在函数调用前,局部变量不占空间,在函数调用时,才为局部变量开辟空间,函数结束,局部变量就释放
//定义普通局部变量
//在函数内部定义的,不加任何修饰的都是局部变量 

void fun()
{
	int num = 100; 
	num++;	
	printf("%d\n", num);
} 

void main()
{
	//局部变量只能在定义的函数内使用 ,使用周期相对较短 ,函数结束,局部变量就会释放 
    fun();
    //printf("%d\n", num);
    
    //num值都一样 
    fun();
    fun();
    fun();
    fun();
}

2.4 静态局部变量

定义局部变量时,前面加static

作用范围:在定义函数或复合句有效

生命周期

  • 第一次调用函数时,开辟空间赋值,函数结束后,不释放
  • 以后在调用该函数,不再为其开辟空间,也不赋初值,用的是之前那个变量。

 静态局部变量就初始化一次

#include <stdio.h> 

//定义静态局部变量
//在函数内部定义的,加static修饰的都是静态局部变量 

void fun()
{
	//如果普通局部变量不进行初始化,则默认是随机变量
	//如果静态局部变量不进行初始化,则默认为0 
	int a; 
	static int num; 
	
	printf("a = %d\n", a);
	printf("num = %d\n", num);
}
void fun1()
{
	//静态变量不会随着当前函数执行结束而释放空间,下次函数使用的是之前的空间
	//静态局部变量只会初始化一次 
	static int num1 = 100;
	num1++;
	printf("num1 = %d\n", num1);
	
 } 

void main()
{
	
    fun();
   
    fun1();
    fun1();
    fun1();
    fun1();
    fun1();
}

 注意:

1、定义普通局部变量,如果不赋初值,值是随机的

     定义静态局部变量,如果不赋初值,默认为0

2、普通全局变量、和静态全局变量如果不赋初值,值为0

三、内部函数和外部函数

3.1 内部函数

内部函数(静态函数):就是用static进行修饰的函数。

作用范围只能在定义的.c中有效

static int ADD(int iNum1, int iNum2)
{
}

static char* GetString(char* pString)
{
	return pString;
}

static void ShowString(char* pString)
{
	printf("%s\n", pString );
}
 
  main()
{
  	char* pMyString;
  	pMyString = GetString("MingRi");
  	ShowString(pMyString);
  	
	printf("\n");
 }

3.2 外部函数

外部函数:可以在程序任何一个文件中调用

在分文件编程中,只需要将函数的实现过程写在指定的.c文件中,然后将其声明写在指定的.h文件中,其他文件主要包含了头文件,就可以使用外部函数

定义外部函数可用extern 进行修饰。在使用一个外部函数时,需先用extern声明所用函数是外部函数

extern int Add(int iNum1, int iNum2);
extern char* GetString(char* pString);
extern void ShowString(char* pString);

 char* GetString(char* pString)
{
	return pString;
}

 void ShowString(char* pString)
{
	printf("%s\n", pString );
}
 
  main()
{
  	char* pMyString;
  	pMyString = GetString("MingRi");
  	ShowString(pMyString);
	printf("\n");
 }

3.3 内部函数与外部函数的区别

       外部函数:在所有地方都可以调用

       内部函数:只能在所定义的.c中函数调用

3.4 扩展

在同一作用范围内,不允许变量重名

作用范围不同的可以重名

局部范围内,重名的全局变量不起作用(就近原则

int num = 100; //全局

int main()
{
   //如果出现可以重名的情况,使用的时候满足向上就近原则

    int num = 999; //局部
    
    return 0;
}

四、内存组织方式(参考)

  程序员将程序编写完成,需要先将程序装载到计算机内核或半导体内存中,然后在运行。

  程序被分成4个逻辑段:

注意:根据操作平台和编辑器不同,堆和栈可使用被所有同时运行的程序共享的操作系统资源,也可使用程序独占局部资源。

4.1 堆与栈

🍖堆:存放动态分配和释放内存块的存储空间

malloc()和free()从堆中动态分配和释放内存。

main()
{
   int *pInt;
   pInt = (int*)malloc(sizeof(int));
   
   *pInt = 100;
   printf("数值是: %d\n", *pInt);
   free(pInt);
    
   	printf("\n");
}	

🍖

程序调用函数和声明局部变量时,系统将自动分配内存。栈——后进先出的压弹式数据结构。

后进先出:在程序运行时,每次向栈中压入一个对象,然后栈指针向下移动一个位置。当系统从栈中弹出一个对象时,最近进栈的对象将被弹出,然后栈指针向上移动一个位置。如果栈指针位于栈顶——栈是空的;如果栈指针指向最下面的数据项的后一个位置——栈为满。

栈如何工作:当一个函数A调用另一个函数B时。

void DisplayB(char* string)
{
	printf("%s\n",string);
}

void DisplayA(char* string)
{
	char String[20] = "LoveWorld";
	printf("%s\n",string);
	DisplayB(String);
}

main()
{
    char String[20] = "LoveChina!";
    DisplayA(String);
	 
   	printf("\n");
}	

五、动态内存申请

5.1 动态分配内存的概述

所谓动态分配内存就是早堆区开辟空间,就是在堆区开辟空间,堆区空间手动申请,手动释放,相对比较灵活

内存中动态分配内存。

在数组章中,介绍过数组的长度是预先定义好的,在整个程序中固定不变,但是在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据,而无法预先确定。为了解决上问题,C语言提供了一些内存管理函数, 这些内存管理函数可以按需要动态的分配内存空间,也可把不再使用的空间回收再次利用。如想用这40字节,就申请使用,不想使用释放。若40个字节不够使用,可以在40字节之后多开辟些,这样也是允许的。


 

固定不变:int a[10]; //当程序运行到此处就会给其开辟40字节的空间,并且最多只能放10个整型的数据,不可超过这个范围,如果超过这个范围,除非重新定义数组,否则无法对其进行操作。

所需的内存空间取决于实际输入的数据:实际需要用多少空间就用多少空间,不像数组直接开辟定长的,

5.2 静态分配、动态分配

①、静态分配

  • 在程序编译或运行过程中,按事先规定大小分配内存空间的分配方式。int a[10]
  • 必须事先知道所需空间的大小
  • 分配在栈区或全局变量区,一般以数组的形式
  • 按计划分配

②、动态分配

  • 在程序运行过程中,根据需要大小自由分配所需空间
  • 按需分配
  • 分配在堆区,一般使用特定函数进行分配

5.3 动态分配函数

①、malloc():

malloc:
头文件: #include<stdlib.h>

函数原型: void *malloc(unsigmed int size);

功能说明:

    在内存的动态存储器(堆区)中分配一块size字节大小连续区域,用来存放类型说明符指定的类型。
malloc 函数会返回void*指针,使用时必须做相应的强制类型转换,分配的内存空间内容不确定,
一般使用memset初始化。

返回值:

        分配空间的起始地址(分配成功)

        Null(分配失败)

main()
{
   int *pInt;				         	 //定义指针pInt,来保存分配内存地址 
   pInt = (int*)malloc(sizeof(int));	//指定大小sizeof,返回指针时也相对应int型指针(强制转换) 
   
   *pInt = 100;
   printf("数值是: %d\n", *pInt);
	 
   	printf("\n");
}	

#include <stdio.h> 
#include <stdlib.h>

char *fun()
{
	//char ch[100] = "hello world"; //因为被释放了,F返回值为NULL 
	
	//静态 全局区的空间只要开辟号,除非程序结束,否则不会释放,所以
	//如果是临时使用,不建议使用静态全局区的空间 
	//static char ch[100] = "hello world"; 
	
	//堆区开辟空间,手动申请,手动释放,更加灵活
	//使用malloc函数的时候一般要进行强制转换,如果不加会警告 
	char *str = (char *)malloc(100 * sizeof(char));
	str[0] = 'h';
	str[1] = 'e';
	str[2] = 'l';
	str[3] = 'l';
	str[4] = 'o';
	str[5] = '\0';
		
	return str;
}

void main()
{
	char *p;
	p = fun();
	printf("p = %s\n", p);
}

注意:

  • 在调用malloc之后,一定要判断,是否申请内存成功。
  • 如果多次malloc申请内存,第一次和第二次申请的内存不一定是连续的
  • 使用malloc开辟空间需要保存开辟好的地址空间的首地址,但是由于不确定空间用于做什么,所以本身返回值类型为void*,所以在调用函数时根据接收者的类型对其进行强制转换

②、calloc():

在内存中动态分配n个长度为size的连续内存空间数组,calloc函数会返回一个指针,该指针指向动态分配的连续内存空间地址,如果分配空间错误时,返回NULL。

calloc:
头文件: #include<stdlib.h>

函数原型: void *calloc(size_t nmemb, size_t size);
    nmemb:要申请的空间的块数
    size:每块的字节数

功能说明:
    在堆区申请指定大小的空间,在内存堆中,申请nmemb块,每块的大小为size个字节的连续区域
  

返回值:

        申请的内存首地址(申请成功)

        Null(申请失败)
main()
{
   int *pArray;	
   int i;			         	 //定义指针pArray
   pArray = (int*)calloc(3, sizeof(int));	//指定大小sizeof和数组个数,pArray指向首地址 
   
	for(i = 0; i < 3; i++)
	{
		*pArray = 10 * i;
		printf("NO :%d  数值是: %d\n", i, *pArray);
		pArray +=1;
	  }  
   	 
   	printf("\n");
}	

注意:malloc和calloc函数都是用来申请内存的

区别:

  1. 函数名不一样
  2. 参数个数不一样
  3. malloc申请的内存,内存中存放的内容是随机的,不确定的,而calloc函数申请的内存中的内容为0 

③、realloc():(重新申请内存)

存在问题:调用malloc和calloc函数单词申请内存是连续的,两次申请的两块内存不一定连续。

还存在,即先用malloc或者calloc申请了一块内存,还想在原先内存的基础上接着申请内存;或开始使用malloc或calloc申请了一块内存,释放后边的一部分内存。

为了解决这个问题,发明了realloc函数

改变ptr指针指向空间大小。size可以是任意大小,返回值是一个指向新地址的指针,如果出现错误,则返回NULL

realloc:
头文件: #include<stdlib.h>

函数原型: void *realloc(void*s, unsigned int newsize);

功能说明:
 
    在原本申请好的堆区空间的基础上重新申请内存,新的空间大小为函数的第二个参数,
    如果原本申请好的空间后面不足以增加指定的大小,系统会重新找一个足够大的位置靠谱指定的空间,然后将原本空间的数据拷贝过来,然后释放原本的空间。
    s:原本开辟好的空间的首地址
    newsize;重新开辟的空间的大小
  

返回值:

    新申请的内存的首地址

 

//增加空间 
void test1()
{
	char *p;
	p = (char *)malloc(100);
	
	//在100个字节后面追加50个字节 
	p =(char *)realloc(p, 150); //p指向的内存的新的大小为 150个字节 
}

//减少空间 
void test2()
{
	char *p;
	p = (char *)malloc(100);
	
	//想重新申请内存,新的大小为50个字节 
	p =(char *)realloc(p, 50); //p指向的内存的新的大小为 50个字节 , 100个字节后面的50字节的空间被释放 
}

void main()
{
	test1();
	test2();
}

注意:malloc、calloc、realloc动态申请的内存,只有在free或程序结束时才释放

④、free()——释放内存函数:

头文件: #include<stdlib.h>

函数原型: void *free(void *ptr); //ptr:开辟后使用完毕的堆区空间的首地址

功能说明:

    free函数释放ptr指向的内存。只能释放堆区空间

注意:

        ptr指向的内存必须是malloc calloc relloc动态申请的内存

使用由指针ptr指向的内存区,使部分内存区能被其他变量使用,ptr是最近一次调用calloc或malloc函数时返回的值。

main()
{
  int *pInt;				         	
   pInt = (int*)malloc(sizeof(int));	
   
   *pInt = 100;
   printf("数值是: %d\n", *pInt);
   
   free(pInt);
   printf("数值是: %d\n", *pInt);
	 
   	printf("\n");
   	printf("\n");
}	

char *fun()
{
	//char ch[100] = "hello world"; //因为被释放了,F返回值为NULL 
	
	//静态 全局区的空间只要开辟号,除非程序结束,否则不会释放,所以
	//如果是临时使用,不建议使用静态全局区的空间 
	//static char ch[100] = "hello world"; 
	
	//堆区开辟空间,手动申请,手动释放,更加灵活
	//使用malloc函数的时候一般要进行强制转换,如果不加会警告 
	char *str = (char *)malloc(100 * sizeof(char));
	str[0] = 'h';
	str[1] = 'e';
	str[2] = 'l';
	str[3] = 'l';
	str[4] = 'o';
	str[5] = '\0';
		
	return str;
}

void main()
{
	char *p;
	p = fun();
	printf("p = %s\n", p);
	
	//使用free函数释放空间 
	free(p);
	//防止野指针 
	p = NULL; 
}

注意:

  •  free函数只能释放堆区的空间,其他区域的空间无法使用free
  •   free释放空间必须释放malloc或calloc或rello的返回值对应空间,不能说只释放一部分
char *p = (char *) malloc(100);
free(p);
  •   free后,因为没有给p赋值,所以p还是指向原先动态申请的内存,但内存已经不能再使用了,p变成野指针。解释:释放空间并不是把内存直接删除,而是告诉系统本人不在使用这个空间了,而别人可以使用这个空间。还会出现一种情况,虽然告知别人这块空间不用了,但该变量p还知道该空间在哪,因为p还保存原先地址,假设一个A同学在班级坐在x1的座位,在班级中这个x1的座位就属于他,但如果这个A同学要换位置呢,这个x1的位置还是存在,但里这个位置坐的内容却不确定,因此,只要你释放内容,尽可能不要去操作了‘。因为不一定这块空间内容是什么。一般为了防止野指针,会在free完毕之后对p赋为NULL。
  • 一块动态申请的内存,只能free一次,不能多次free.

六、内存泄露

内存泄漏:首地址丢了,找不到,再也没法使用了 ,也没法释放了,这块内存就被泄露了。

 使用malloc()等函数分配内存后,需要使用free()函数进行释放。若不释放,会造成内存泄漏,从而导致系统崩溃。

易造成漏删除的:

pOld = (int*)malloc(sizeof(int));
pNew = (int*)malloc(sizeof(int));

pOld = pNew;

free(pOld);

free(pOld)释放的是pOld所指向的内存空间即原来pNew,但pOld原来指向的内存空间还没有被释放。

6.1 内存泄漏案例

内存泄漏案例1:

int main()
{
    char *p;
    p = (char *)malloc(100);
    p = "hello world"; // p指向其他地方,把字符串的首地址赋给p;这样子导致原本堆区的空间找不到了
    //从此刻起,再也找不到申请的100个字节的空间,动态申请的100个字节被泄漏了
    return 0;
}

内存泄漏案例2:

void test1()
{
	char *p;
	p =(char *)malloc(100); 
	//接下来,可以用p指向内存了 , 
	//...
	//没有释放,没有返回,这样导致这个函数执行完后不知道这个申请的内存空间在哪了 
}

void main()
{
	//每次用一次fun泄露100个字节 
	test1();
	test1();

}

如果在代码中出现这2种情况,一定要及时解决,否则后面内存就不够用了

6.2 解决方式

解决方式1

void test1()
{
	char *p;
	p =(char *)malloc(100); 
	//接下来,可以用p指向内存了 , 
	//...
	free(p); 
}

void main()
{
	//每次用一次fun泄露100个字节 
	test1();
	test1();
}

 解决方式2:

char *test1()
{
	char *p;
	p =(char *)malloc(100); 
	//接下来,可以用p指向内存了 , 
	//...
	return p;
}

void main()
{
	char *q;
	//可以通过q调用动态申请的100个字节的内容 
	q = test1();
	
	释放 
	free(q);
    //防止野指针
    p =  NULL;
}

总结:申请的内存,一定不要把首地址给丢了,在不用的时候一定要释放内存。也要防止野指针

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值