首页 > 系统相关 >【C语言】内存分区

【C语言】内存分区

时间:2024-04-07 20:01:59浏览次数:15  
标签:函数 int 分区 数据类型 C语言 char 内存 printf void

【C语言】内存分区


文章目录


一、数据类型

数据类型概念

什么是数据类型?为什么需要数据类型?
数据类型是为了更好进行内存的管理,让编译器能确定分配多少内存。
数据类型可以理解为创建变量的模具: 固定大小内存的别名;

在这里插入图片描述

typedef

1、给数据类型起别名 typedef 原名 别名
示例代码:
方法一:

struct Person{
	char name[64];
	int age;
};

typedef struct Person myperson;

void test(){
	myperson p;	//相当于 struct Person p;
}

方法二:

typedef struct Person
{
	char name[64];
	int age;
}myPerson;

void test()
{
	myPerson p;	//相当于 struct Person p;
}

2、区分数据类型

定义变量
char* p1,p2; //实际上定义的变量p1尾char类型,p2为char类型。初学C语言的同学可能会混淆
使用typedef
typrdef char
pchar;
pchar p1,p2; //p1,p2均为char*类型

void数据类型

void字面意思是”无类型”,void* 无类型指针,无类型指针可以指向任何类型的数据。
void定义变量是没有任何意义的,当你定义void a,编译器会报错。原因是:无法给void无类型变量分配内存。
void真正用在以下两个方面:
对函数返回的限定;
对函数参数的限定;

示例代码:

//1. void修饰函数参数和函数返回
void test01(void){
	printf("hello world");
}

//2、void *   万能指针  不管几级指针,任意类型指针都是4个字节
void test02()
{
	//printf("size of void* = %d\n", sizeof(void *));//4
	void * p = NULL;

	int * pInt = NULL;
	char * pChar = NULL;

	pChar = (char *)pInt;//需要强制类型转化才行

	pChar = p; //万能指针  可以不通过强制类型转换就转成其他类型指针

}

sizeof 操作符

sizeof是C语言中的操作符并不是函数,类似于++、–等等。sizeof能够告诉我们编译器为某一特定数据或者某一个类型的数据在内存中分配空间时分配的大小,大小以字节为单位。

sizeof(变量);
sizeof 变量; //这里可以看出sizeof 不是函数
sizeof(类型);

sizeof的返回值类型是 无符号整型 unsigned int

//当unsigned int 和 int做运算,会转换成统一 unsigned int数据类型
void test()
{
	if(sizeof(int)-5>0)
	{
		printf("大于0\n");
	}
	else
	{
		printf("小于0\n");
	}
}//打印结果为大于0,故可知sizeof的返回值是unsigned int

sizeof 用途
1.统计数组长度
当数组名做函数参数时候,会退化为指针,指向数组中第一个元素的位置

示例代码:

void calculateArray(int arr[])
{
	printf("array length = %d\n", sizeof(arr));//4
}
void test()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
	//printf("array length = %d\n", sizeof(arr));//32
}

总结

  • 数据类型本质是固定内存大小的别名,是个模具,C语言规定:通过数据类型定义变量;

  • 数据类型大小计算(sizeof);

  • 可以给已存在的数据类型起别名typedef;

  • 数据类型的封装(void 万能类型);


二、变量

变量的概念

既能读又能写的内存对象,称为变量;
若一旦初始化后不能修改的对象则称为常量。
变量定义形式: 类型 标识符, 标识符, … , 标识符

变量的修改方式

void test()
{
	//1、直接修改
	int a = 10;
	a = 20;

	//2、间接修改 
	int * p = &a;
	*p = 30;
	printf("a = %d\n", a);
}
struct Person
{
	char a; //0 ~ 3			//内存对齐
	int b;  //4 ~ 7
	char c; //8 ~ 11		//内存对齐
	int d;  //12 ~ 15
};
void test()
{
	struct Person p = { 'a', 10, 'b', 20 };
	//直接修改 d属性
	p.d = 1000;
	//间接修改 d属性
	struct Person * pp = &p;
	pp->d = 1000;
	
	char * pp = &p;
	*(int*)(pp + 12) = 2000;
	printf("d属性为: %d\n", *(int*)(pp + 12));		//指针步长
	printf("d属性为: %d\n", *(int*)((int*)pp + 3));
}


三、程序内存分区模型

内存分区

我们要执行我们编写的C程序,那么第一步需要对这个程序进行编译。
1)预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法
2)编译:检查语法,将预处理后文件编译生成汇编文件
3)汇编:将汇编文件生成目标文件(二进制文件)
4)链接:将目标文件链接为可执行程序

在没有运行程序前,也就是说程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分(有些人直接把data和bss合起来叫做静态区或全局区)

总体来讲说,程序源代码被编译之后主要分成两种段:程序指令(代码区)和程序数据(数据区)。代码段属于程序指令,而数据域段和.bss段属于程序数据。

程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。然后,运行可执行程序,操作系统把物理硬盘程序load(加载)到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区。

五区特征:
代码区(text segment):可共享,只读。
未初始化数据区(BSS),全局初始化数据区/静态数据区(data segment):生存周期为整个程序运行过程。
栈区(stack):先进后出的内存结构,存放函数的参数值、返回值、局部变量等。生存周期为申请到释放该段栈空间。
堆区:它的容量要远远大于栈(不是无限大),用于动态内存分配。由程序员手动分配和释放。
类型 作用域 生命周期 存储位置

类型作用域生命周期存储位置
auto变量一对{}内当前函数栈区
static局部变量一对{}内整个程序运行期初始化在data段,未初始化在BSS段
extern变量整个程序整个程序运行期初始化在data段,未初始化在BSS段
static全局变量当前文件整个程序运行期初始化在data段,未初始化在BSS段
extern函数整个程序整个程序运行期代码区
static函数当前文件整个程序运行期代码区
register变量一对{}内当前函数运行时存储在CPU寄存器
字符串常量当前文件整个程序运行期data段

栈区

由系统进行内存的管理。主要存放函数的参数以及局部变量。在函数完成执行,系统自行释放栈区内存,不需要用户管理。

char * getString()
{
	char str[] = "hello world";
	return str;
}
void test()
{
	char * p = NULL;
	p = getString();
	printf("%s\n", p);
}
int main(){
	test();
	return 0;
}

char * getString()函数返回str的地址函数结束所开辟的栈空间也随之释放str里存放的内容就位置p的内容为0x02所对应的内容也就位置故打印出来的结果不是hello world 而是乱码。
在这里插入图片描述

堆区

由编程人员手动申请,手动释放,若不手动释放,程序结束后由系统回收,生命周期是整个程序运行期间。使用malloc或者new进行堆的申请。
示例代码:

int* getSpace()
{
	int* p = malloc(sizeof(int) * 5);
	if (p == NULL)
	{
		return;
	}
	for (int i = 0; i < 5; i++)
	{
		p[i] = i + 100;
	}
	return p;
}

void test01()
{
	int* p = getSpace();
	for (int i = 0; i < 5; i++)
	{
		printf("%d\n", p[i]);
	}
	//手动开辟  手动释放
	free(p);
	p = NULL;

}


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

堆区分配内存注意事项(指针分配内存):

//error
void allocateSpace(char * pp)
{
	char * temp  =  malloc(100);
	memset(temp, 0, 100);
	strcpy(temp, "hello world");
	pp = temp;
}

void test02()
{
	char * p = NULL;
	allocateSpace(p);
	printf("%s\n", p);
}
int main() {
	test02();
	return 0;
}

在这里插入图片描述

//true
void allocateSpace2(char ** pp)
{
	char * temp = malloc(100);
	memset(temp, 0, 100);
	strcpy(temp, "hello world");

	*pp = temp;

}

void test03()
{
	char * p = NULL;
	allocateSpace2(&p);
	printf("%s\n", p);

	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
}
int main() {
	test03();
	return 0;
}

在这里插入图片描述

全局/静态区

全局静态区内的变量在编译阶段已经分配好内存空间并初始化。这块内存在程序运行期间一直存在,它主要存储全局变量静态变量常量

//static 静态变量
// 特点:在运行前分配内存,程序运行结束 生命周期结束 ,在本文件内都可以使用静态变量
// 全局作用域 a
static int a = 10;

void test01() {
//局部作用域 b
static int b = 20;
}

int main(){

//告诉编译器 下面代码中出现 g_a 不要报错,是外部链接属性,在其他文件中
extern int g_a;
printf(“g_a = %d\n”, g_a);
system(“pause”);
return 0;
}

常量区

1、const修饰的变量


//全局变量
const int a = 10; //常量区 ,间接修改 语法通过,运行失败,原因:受到常量区的保护
void test01()
{
	//a = 100;

	int * p = &a;
	*p = 100;
	printf("%d\n", a);
}
void test02()
{
	const int b = 10; //存放在栈上,通过间接修改是可以成功的

	//b = 20;

	int * p = &b;
	*p = 20;
	printf("%d\n", b);

	//在C语言中 const修饰的局部变量  ,不可以初始化数组 ,称为伪常量
	//int arr[b];
}

2、字符串常量

ANSI C中规定:修改字符串常量,结果是未定义的。
ANSI C并没有规定编译器的实现者对字符串的处理,例如:
1.有些编译器可修改字符串常量,有些编译器则不可修改字符串常量。
2.有些编译器把多个相同的字符串常量看成一个(这种优化可能出现在字符串常量中,节省空间),有些则不进行此优化。如果进行优化,则可能导致修改一个字符串常量导致另外的字符串常量也发生变化,结果不可知。
所以尽量不要去修改字符串常量!

void test03()
{
	char * p1 = "hello world";
	char * p2 = "hello world";
	char * p3 = "hello world";
	printf("%d\n", &"hello world");
	printf("%d\n", p1);
	printf("%d\n", p2);
	printf("%d\n", p3);//用vs下p1,p2,p3的内容是同一个
}

四、函数调用模型

宏函数

#define MYADD(x,y) ((x) + (y))
//1、宏函数需要加小括号修饰,保证运算的完整性
//2、通常会将频繁、短小的函数 写成宏函数
//3、宏函数 会比普通函数在一定程度上 效率高,省去普通函数入栈、出栈时间上的开销
// 优点: 以空间 换时间

void test01()
{
	printf("%d\n", MYADD(10, 20) * 20 ); //  ((10) + (20)) * 20

}

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

函数调用流程

栈容器必须遵循一条规则:先入栈的数据最后出栈(First In Last Out,FILO).
一个函数调用过程所需要的信息一般包括以下几个方面:

  • 函数的返回地址;
  • 函数的参数;
  • 临时变量;
  • 保存的上下文:包括在函数调用前后需要保持不变的寄存器。

在这里插入图片描述
这边抛出两个问题
1:函数形参入栈时候,参数传递数据是从左向右还是从右向左?
2:a和b是由谁管理释放?是main函数(主调函数)管理还是func函数(被调函数)管理?

调用惯例

函数的调用方和被调用方对于函数是如何调用的必须有一个明确的约定,只有双方都遵循同样的约定,函数才能够被正确的调用,这样的约定被称为”调用惯例”

在c语言里,存在着多个调用惯例,而默认的是cdecl.任何一个没有显示指定调用惯例的函数都是默认是cdecl惯例。比如我们上面对于func函数的声明,它的完整写法应该是:
int cdecl func(int a,int b); 注意:
cdecl不是标准的关键字,在不同的编译器里可能有不同的写法,例如gcc里就不存在_cdecl这样的关键字,而是使用__attribute
((cdecl)).

调用惯例出栈方参数传递名字修饰
cdecl函数调用方从右至左参数入栈下划线+函数名

这里就可以回答上面的两个问题了
1.函数形参入栈时候,参数传递数据是从右向左。
2.a和b是由是main函数(主调函数)管理释放。

函数变量传递分析

变量的作用域使用规则

char * func()
{
	char * p =  malloc(10); //堆区数据,只要没有释放,都可以使用
	int c = 10;//在func中可以使用,test01和main都不可以使用
	return p;
}

void test01()
{
	int b = 10; // 在test01 、func 可以使用,在main中不可以用

	func();
}

int main(){

	int a = 10; //在main 、test01 、 func中都可以使用

	test01();

	return 0;
}

栈的生长方向

问题1:栈内数据储存是由高地址到低地址还是由低地址到高地址?
问题2:单个数据的高位字节空间存放在栈区高地址还是低地址?

void test01()
{
	int a = 10;  //栈底  高地址
	int b = 10;
	int c = 10;
	int d = 10;  //栈顶  低地址


	printf("%p\n", &a);
	printf("%p\n", &b);
	printf("%p\n", &c);
	printf("%\n", &d);

}

在这里插入图片描述
这里可以看出栈内存储是由高地址向低地址方向。

void test02()
{
	int a = 0x11223344;

	char * p = &a;

	printf("%x\n", *p);    //44  低位字节数据  低地址
	printf("%x\n", *(p+1)); //33  高位字节数据  高地址
}

这里可以看出低位字节数据位于低地址,高位字节数据位于高地址(小端模式)。
在这里插入图片描述

总结

到这里这篇文章的内容就结束了,谢谢大家的观看,如果有好的建议可以留言喔,谢谢大家啦!

标签:函数,int,分区,数据类型,C语言,char,内存,printf,void
From: https://blog.csdn.net/2301_80035097/article/details/137126449

相关文章

  • 【C语言】贪吃蛇
    【C语言】贪吃蛇文章目录【C语言】贪吃蛇前言模块描述效果展示完整代码代码拆分定义蛇对象定义食物对象初始化蛇对象食物的初始化修改控制台光标位置画蛇和食物蛇移动控制基础知识游戏控制逻辑实现,接收键盘输入蛇的移动控制画墙设置光标不可见加速和显示成绩总结前......
  • 每日一题:C语言经典例题之小球蹦蹦跳跳
    题目描述调皮的小明将皮球从100m的高度自由落下,每次落地后反弹回原高度的一半,再落下,再反弹。求它在第N次落地时,共经过了多少米,第N次反弹多高。输入一个正整数N,表示球落地的次数。输出 length=球第N次落地时所经过了距离high=球第N次落地反弹的高度小数点后保留4位......
  • Redis的前世今生(内存管理、持久化、高可用、集群 详解)一看就懂
    Redis的诞生:    redis的诞生和mysql脱不了关系,在redis还未出现时,用户的每次请求都是直接访问mysql,渐渐的人们发现,请求大部分都是读操作,而且很多都是重复的数据,磁盘的i/o是很慢的,所以人们就想,能不能学学cpu建立的缓存机制,mysql也搞一个缓存,就这样一个基于内存的数据库......
  • C语言文件操作
    本篇文章从文件是什么,为什么使用文件,到怎么使用文件来介绍文件。一.文件是什么?磁盘(硬盘)上的文件是文件。但是在程序设计中,我们一般谈的文件有两种:程序文件、数据文件(从文件功能的角度来分类的)。1.文件名文件名包含3部分:文件路径+文件名主干+文件按后缀。2.程序文件程序......
  • Spring内存马分析
    环境搭建踩了很多坑....,不过还好最后还是成功了IDEA直接新建javaEE项目,然后记得把index.jsp删了,不然DispatcherServlet会失效导入依赖:<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId&g......
  • C语言游戏实战(11):贪吃蛇大作战(多人对战)
         成果展示:贪吃蛇(多人对战) 前言:这款贪吃蛇大作战是一款多人游戏,玩家需要控制一条蛇在地图上移动,吞噬其他蛇或者食物来增大自己的蛇身长度和宽度。本游戏使用C语言和easyx图形库编写,旨在帮助初学者了解游戏开发的基本概念和技巧。在开始编写代码之前,我们需要先......
  • C语言05-(跳转控制语句、断点调试)
    8.3跳转控制语句①break在switchcase结构中使用:跳出所在的case在循环结构(dowhile、while、for)中使用:跳出整个循环,循环结束注:跳出所在的循环!②continue在循环结构(dowhile、while、for)中使用:跳出本次循环,下次继续注:跳出所在的循环!③goto1.使用goto......
  • c语言程序实验————实验报告四
    c语言程序实验————实验报告四实验项目名称:实验报告2数据描述实验项目类型:验证性实验日期:2024年3月21日一、实验目的1.在熟练掌握if语句和switch语句的基础上,能灵活使用if语句和switch语句进行选择结构的程序设计2.学习调试程序二、实验硬、软件环境Windows计......
  • 操作系统综合题之“采用动态分区分配算法下的3种算法(首次适应算法、循环首次适应算法
    一、问题:当空闲链如下图,第一个空闲分区起始地址为20KB,大小为120KB;第二个空闲分区起始地址为200KB,大小为100KB;第三个空闲分区起始地址为400KB,大小为60KB。若某进程P1先申请大小为30KB的内存空间,随后进程P2再申请大小为20KB的内存空间,画出给P1分配完之后的空闲链和给P2分配完......
  • C语言高效的网络爬虫:实现对新闻网站的全面爬取
    1.背景搜狐是一个拥有丰富新闻内容的网站,我们希望能够通过网络爬虫系统,将其各类新闻内容进行全面地获取和分析。为了实现这一目标,我们将采用C语言编写网络爬虫程序,通过该程序实现对news.sohu.com的自动化访问和数据提取。2.网络爬虫系统设计2.1网络请求与响应处理......