C语言的内存四驱模型

一、数据类型的本质

    1、数据类型可以理解为创建变量的模具,是固定内存大小的别名。

    2、数据类型的作用:编译器预算对象(变量)分配内存空间大小

二、void

1)、void简介

     void 的字面意思是“无类型”,void*则为“无类型指针”,void *可以指向任何数据类型。在c中不存在void类型的变量,因为c语言就没有定义void究竟是多大内存的别名。c语言中void有两种使用方式:

      1、用于修饰函数的返回值和函数参数,仅表示无,如果一个函数没有返回值,怎将其声明为void,如果没有参数,应该声明

         其参数为void

      2、用于数据类型的封装,例如: int InitHardEnv(void **handle)

2)、void指针的意义

      c语言规定只有相同数据类型的指针才可以相互赋值,因此void*指针作为左值用于“接收”任意类型的指针。void*指针作为右值赋值给其他指针时,需要进行强制类型转换。

三、变量

1)、概念:

        既能读又能写的内存对象,我们称为变量;一旦初始化后不能修改的对象我们称之为常量。

2)、变量的本质

        a、程序通过变量来申请和命名内存空间,例如:int a =0 。程序通过变量名访问内存空间(变量名实质是

             一段连续内存空间的别名)

四、C语言程序的内存四区模型。

1)、内存四区的建立流程

                

        流程说明:

                    a、操作系统把物理硬盘代码load代内存(运行.exe文件)

                    b、操作系统把c代码分成四个区

                    c、操作系统找到main函数入口执行程序。

2)各个区元素分析:

栈区(stack) 由编译器自动分配释放,存放函数的参数值,局部变量值等
堆区(heap) 一般由程序员分配与释放(动态内存申请与释放),若程序员不释放,程序结束后可能由操作系统进行回收

全局区(静态区)(static)

全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未初始化的

全局变量和未初始化的静态变量在相邻的另一块区域,该区域在程序结束后由操作系统释放。

常量区 字符串常量和其他常量的存储位置,程序结束后由操作系统进行释放
程序代码区   存放函数体的二进制代码
 3)静态存储区的理解:   

        执行面代码:

#define  _CRT_SECURE_NO_WARNINGS 
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
char * getStr1()
{
	char *p1 = "abcdefg2";
	return p1;
}
char *getStr2()
{
	char *p2 = "abcdefg22";
	return p2;
}
void main()
{
	char *p1 = NULL;
	char *p2 = NULL;
	p1 = getStr1();
	p2 = getStr2();
	//打印p1 p2 所指向内存空间的数据
	printf("p1:%s , p2:%s \n", p1, p2);
	//打印p1 p2 的值
	printf("p1:%d , p2:%d \n", p1, p2);
	printf("hello...\n");
	system("pause");
	return;
}

输出:

p1:abcdefg2 , p2:abcdefg22
p1:19229488 , p2:19229500
hello...
请按任意键继续. . .

        我们看到输出的p1和p2的值不一样,其指向的内存的地址的内容也不一样。那么我们将代码做如下修改:

 将函数getstr1() 

char *p1 = "abcdefg2";

改为下面语句

char *p1 = "abcdefg22";

       这个时候我们的指针p1 和 p2 所指向的内从空间的值就已经相同了,那么此时p1和p2的值相等吗?

我们运行代码输出:

p1:abcdefg22 , p2:abcdefg22
p1:16870192 , p2:16870192
hello...
请按任意键继续. . .

p1 此时和 p2 的值是相等的。我们从内存四区中的静态存储区来看这段代码:

        函数从main()函数入口执行

char *p1 = NULL;
char *p2 = NULL;

    此时对应的内存四区模型为:

    

编译器分别将变量p1,p2在栈区压栈,并给定初始值为NULL

p1 = getStr1();

当执行到这句代码时,编译器先进性函数调用,进入getstr1函数中去(我们假设全局区首地址为0xaa11):

char * getStr1()
{
	char *p1 = "abcdefg2";
	return p1;
}
     

       当将函数的返回值赋给p1的时候,编译器会将getstr()函数在运行期其间申请的所有的栈区内存进行释放。内存四区如下:


       同一样,当执行p2 = getStr2();语句时基本的内变化和p1相同,唯一不同的是编译器会对我我们的代码进行优化,p2指向的内存的值和p1相同时,编译器会只保留一个。这样内存四区如下:


 4)堆区的理解:

       我们根据下面代码来帮助我们理解堆区,堆区内存是由程序员申请和释放的,由操作系统进行管理,只有当程序进行完后,未被程序员释放的内存才会被释放。

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
//堆
char *getMem(int num)
{
	char *p1 = NULL;
	p1 = (char *)malloc(sizeof(char) * num);    #申请内存空间
	if (p1 == NULL)
	{
		return NULL;
	}
	return p1;
}
void main61()
{
	char *tmp = NULL;
	tmp = getMem(10);
	if (tmp == NULL)
	{
	    return;
	}
	strcpy(tmp, "111222"); //向tmp做指向的内存空间中copy数据
	printf("hello..tmp:%s.\n", tmp);
	system("pause");
	return;
}

输出:

hello..tmp:111222.
请按任意键继续. . .
执行
char *tmp = NULL;

内存四区:

 

当执行到getMem()函数体内的时候(假设堆的首地址是0xaa11)


将返回值赋给tmp时:


执行strcpy()函数之后。


5)栈区的理解:

      我们根据下面代码来帮助我们理解堆区,栈区内存由编译器自动分配与释放

#define  _CRT_SECURE_NO_WARNINGS 
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

//栈
//注意 return不是把内存块 64个字节,给return出来,
//而是把内存块的首地址 ,返回给 tmp
// 理解指针的关键,是内存. 没有内存哪里的指针 
char * getMem2()
{
	char buf[64];		//临时变量 栈区存放
	strcpy(buf, "123456789");
	return buf;
}
void main()
{
	char *tmp = NULL;
	tmp = getMem2();
	printf("hello..tmp:%s.\n", tmp);
	system("pause");
	return;
}

输出:

hello..tmp:烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫烫\?烫烫烫烫烫烫烫烫8??-.
请按任意键继续. . .

 程序出现了输出出现了乱码,我们从内存的角度分析:

char *tmp = NULL;


当执行到getMem2()函数体内的时候(假设buff的首地址是0xaa11)


       当我们的getMem()函数return时,编译器会将属于该函数的栈区的内存数据进行清空,但是buff的值还是会赋给tmp。

        

       我们发现此时因为栈区的buff已经在函数执行完毕时,被编译器释放掉了,tmp此时指向的栈区的内存的是一个随机值,因此我们输出是乱码。

        C语言可以在栈上分配内存,可以在堆上分配作内存,因此作为C语言的学习者我们一定要理解内存,时刻知道我们的内从从哪里来(堆区,栈区,全局区),到哪里去(释放)

 


猜你喜欢

转载自blog.csdn.net/m0_37717595/article/details/80364339