5-1程序中内存从哪里来

程序执行依赖内存,操作系统通过栈、堆和数据区进行内存管理。栈自动分配回收,适用于临时变量,可能引发溢出;堆由程序手动申请释放,适合大块内存需求,可能存在内存泄漏。数据区包括初始化的全局变量和静态局部变量。了解这些内存管理有助于优化程序性能。
摘要由CSDN通过智能技术生成

五、数组&字符串&结构体&共用体&枚举

5.1、程序中内存从哪里来

5.1.1 程序执行需要内存支持

(1)对程序来说,内存就是程序的立足之地(程序是被放在内存中运行的);程序运行时需要内存来存储一些临时变量。

5.1.2 内存管理最终是操作系统完成的

(1)内存本身在物理上是一个硬件器件,由硬件系统提供。
(2)内存由操作系统统一管理。为了内存管理方便又合理,操作系统提供了多种机制来让我们应用程序来使用内存。这些机制彼此不同,各自有各自的特点,我们程序根据自己的实际情况来选择某种方式获取内存(在操作系统处登记这块内存的临时使用权限)、使用内存、释放内存(向操作系统归还这块内存的使用权限)。用酒店开房来类比这个案例是非常合适的。

5.1.3 三种内存来源:栈(stack)、堆(heap)、数据区(data)

(1)在一个C语言程序中,能够获取的内存就是三种情况;栈、堆、数据区;

5.1.4 栈的详解

(1)运行时自动分配&自动回收:栈是自动管理的,程序员不需要手工干预,方便简单;
(2)反复使用:栈内存在程序中其实就是那一块空间,程序反复使用这一块空间。
(3)脏内存:栈内存由于反复使用,每次使用后程序不会去清理,因此分配到时保留原来的值。
(4)临时性:(函数不能返回栈变量的指针,因为这个空间是临时的)

#include <stdio.h>
//函数不能返回函数内部局部变量的地址,因为这个函数执行完返回后这个局部变量已经不在了,这个变量是分配在栈上的,虽然不在了但是栈内存还在可以访问,但是访问时实际上这个内存地址已经和当时那个变量无关了。
int *func(void)
{	
	int a = 4;		//a是局部变量,分配在栈上又叫栈变量,又叫临时变量	
	printf("&a = %p\n",&a);	
	return &a;
} 
int main(void)
{
	int *p = NULL;	
	p = func();	
	printf("p = %p\n",p);	
	printf("*p = %d\n",*p);	//段错误	
	return 0;
}

(5)栈会溢出:因为操作系统事先给定了栈的大小,如果在函数中无穷尽的分配栈内存总能用完。

#include <stdio.h> 
void stack_overflow(void)
{
	int a[10000000] = {0};//段错误,栈溢出
}
void  stack_overflow2(void)
{
	int a = 2;	
	stack_overflow2();//递归实现栈溢出
} 
int main(void)
{
	//stack_overflow();	
	stack_overflow2();	
	return 0;
}

5.1.5 堆内存详解

(1)操作系统堆管理器管理:堆管理器是操作系统的一个模块,堆管理内存分配内存灵活,按需分配;
(2)大块内存:堆内存管理着总量很大的操作系统内存块,各进程可以按需申请使用,使用完释放。
(3)程序手动申请&释放:手动的意思就是需要写代码去申请malloc和释放free;
(4)脏内存:堆内存也是反复使用的,而且使用者用完释放前不会清除,因此也是脏的。
(5)临时性:堆内存只在malloc和free之间属于我这个进程,而可以访问。在malloc之前和free之后都不能再访问,否则会有不可预料的后果。

5.1.6 堆内存使用范例

(1)void *是个指针类型,malloc返回的是一个void *类型的指针,实质上malloc返回的是堆管理器分配给我本次申请的那段内存空间的首地址(malloc返回的值其实是一个数字,这个数字表示一个内存地址)。为什么要使用void *作为类型?主要原因是malloc帮我们分配内存时只是分配了内存空间,至于这段空间将来用来存储什么类型的元素malloc是不关心的,由我们程序来决定。
(2)什么是void类型。早期被翻译成空型,这个翻译非常不好,会误导人。void类型不表示没有类型,而表示万能类型。void的意思就是说这个数据的类型当前是不确定的,在需要的时候可以再去指定它的具体类型。void *类型是一个指针类型,这个本身占4个字节,但是指针指向的类型是不确定的,换句话说这个指针在需要的时候可以被强制转化成其它任何一种确定类型的指针,也就是说这个指针可以指向任何类型的元素。
(3)malloc的返回值:成功申请空间后返回这个内存空间的指针,申请失败时返回NULL,所以malloc获取的内存指针使用前一定要先检验是否为NULL;
(4)malloc申请的内存使用完后要free释放。free§会告诉堆管理器这段内存我用完了,你可以回收了。堆管理器回收了这段内存后这段内存当前进程就不应该被使用了。因为释放后堆管理器就可能把这段内存再次分配给别的进程,所以你就不能再使用了。
(5)再调用free归还这段内存之前,指向这段内存的指针p一定不能丢(也就是不能给p另外赋值)。因为p一旦丢失,这段malloc来的内存就永远的丢失了(内存泄露),直到当前程序结束时操作系统才会回收这段内存。

#include <stdio.h>
#include <stdlib.h> 
int main(void)
{
	//需要一个1000个int类型元素的数组	
	//第一步:申请和绑定	
	int *p = (int *)malloc(1000*sizeof(int));	
	//第二步:检验分配是否成功	
	if(NULL == p)	
	{		
		printf("malloc error\n");		
		return -1;	
	}	
	//第三步:使用申请到的内存	
	//p = &a;	
	//如果在free之前给p另外赋值,那么malloc申请的那段内存就丢失了				
	//malloc后p和返回的内存相绑定,p是那段内存在当前进程的唯一联系人				
	//如果p没有free之前就丢了,那么这段内存就永远丢了。丢了的概念就是				
	//在操作系统的堆管理器中这段内存是当前进程拿着的,但是你也用不了				
	//所以你想申请新的内存来替换使用,这就叫程序“吃内存”,学名叫内存泄露。	
	*(p+0) = 1111;	
	*(p+1) = 2222;	
	printf("*(p+0) = %d\n",*(p+0));	
	printf("*(p+1) = %d\n",*(p+1));	
	//第四步:释放;	
	free(p);   
	p = NULL;
	//防止野指针		
	//释放后测试一下能否继续访问该内存空间
	//依然可以访问,但是这是不正确的,不应该这样用	
	//*(p+100) = 1;	
	//*(p+111) = 2;	
	//printf("*(p+100) = %d\n",*(p+100));	
	//printf("*(p+111) = %d\n",*(p+111));	
	return 0;
}

5.1.7 malloc的一些细节表现

(1)malloc(0):malloc申请0字节内存本身就是一件无厘头事情,一般不会碰到这个需要。如果真的去malloc(0)返回的是NULL还是一个有效指针?答案是:实际分配了16Bytes的一段内存并且返回了这段内存的地址。这个答案不是确定的,因为C语言并没有明确规定malloc(0)时的表现,由各malloc函数库的实现者来定义的。

#include <stdio.h>
#include <stdlib.h> 
int main(void)
{
	int *p1 = (int *)malloc(0);	
	int *p2 = (int *)malloc(0);	
	if(NULL == p1 || NULL == p2)	
	{		
		printf("malloc error\n");		
		return -1;	
	}	
	printf("p1 = %p\n",p1);//p1 = 0x25c6010	
	printf("p2 = %p\n",p2);//p2 = 0x25c6030							
	//p2-p1 = 32 Bytes	
	return 0;
}

(2)malloc(4):GCC中的malloc默认最小是以16字节为分配单位的。如果malloc小于16字节的大小时都会返回一个16字节的大小的内存。malloc实现时没有实现任意字节的分配而是允许一些大小的块内存的分配。

#include <stdio.h>
#include <stdlib.h> 
int main(void)
{
	int *p1 = (int *)malloc(4);	
	int *p2 = (int *)malloc(4);	
	if(NULL == p1 || NULL == p2)	
	{		
		printf("malloc error\n");		
		return -1;	
	}	
	printf("p1 = %p\n",p1);	
	printf("p2 = %p\n",p2);//p2-p1 = 32Bytes								
	return 0;
}

(3)malloc(20)去访问第25、第250、第2500…会怎么样?实战中:第120字节处正确,第1200字节处正确,…终于继续往后访问总有一个数字处开始段错误了。

#include <stdio.h>
#include <stdlib.h> 
int main(void)
{
	int *p1 = (int *)malloc(20);	
	if(NULL == p1)	
	{		
		printf("malloc error\n");		
		return -1;	
	}	
	*(p1+3) = 12;	
	*(p1+3000) = 120;	
	printf("*(p1+3) = %d\n",*(p1+3));	
	printf("*(p1+3000) = %d\n",*(p1+3000));							
	return 0;
}

5.1.8 代码段、数据段、bss段

(1)编译器在编译程序的时候,将程序中的所有的元素分成了一些组成部分,各部分构成一个段,所以段是可执行程序的组成部分。
(2)代码段:代码段就是程序中的可执行部分,直观理解代码段就是函数堆叠组成的。
(3)数据段(也被称为数据区、静态数据区、静态区):数据段就是程序中的数据,直观理解就是C语言程序中的全局变量。(注意:全局变量才算是程序的数据,局部变量不算是程序的数据,只能算是函数的数据)。
(4)bss段(又叫ZI(zero initial)段):bss段的特点就是被初始化为0,bss段本质上也是属于数据段,bss段就是被初始化为0的数据段。
注意区分:数据段(.data)和bss段的区别和联系:二者本来没有本质区别,都是用来存放C程序中的全局变量的。区别在于显式初始化非零的全局变量存在.data中,而把显式初始化为0或者并未显式初始化(C语言规定未显式初始化的全局变量值默认为0)的全局变量存在bss段。

5.1.9 有些特殊数据会被放到代码段

(1)C语言中使用Char *p = “LINUX”;定义字符串时,字符串“LINUX”实际被分配在代码段,也就是说这个“LINUX”字符串实际上是一个常量字符串而不是变量字符串。

#include <stdio.h> 
int main(void)
{
	char *p = "Linux";	
	// const  char *p = "Linux";	//最正确的写法应该是这个	
	*(p+0) = 'f';		
	printf("p = %s\n",p);//发生段错误	
	return 0;
}

(2)const型常量:C语言中const关键字用来定义常量,常量就是不能被改变的量。const的实现方法至少有2种:第一种就是编译器将const修饰的变量放在代码段去以实现不能修改(普遍见于各种单片机的编译器);第二种就是由编译器来检查以确保const型的常量不会被修改,实际上const型的常量还是和普通变量一样放在数据段的(gcc中就是这样实现的)。

5.1.10 显式初始化为非零的全局变量和静态局部变量放在数据段

(1)放在.data段的变量有2种:第一种是显式初始化为非零的全局变量。第二种是静态局部变量,也就是static修饰的局部变量。(普通局部变量分配在栈上,静态局部变量分配在.data段)

5.1.11 未初始化或显式初始化为0的全局变量放在bss段

(1)bss段和.data段并没有本质区别,几乎可以不用明确去区分这两种。

5.1.12 总结:C语言中所有变量和常量所使用的内存无非以上三种情况

(1)相同点:三种获取内存的方法,都可以给程序提供可用内存,都可以用来定义变量给程序用。
(2)不同点:栈内存对应C中的普通局部变量(别的变量还用不了栈,而且栈是自动的,由编译器和运行时环境共同来提供服务的,程序员无法手工控制);堆内存完全是独立于我们的程序存在和管理的,程序需要内存时可以去手工申请malloc,使用完成后必须尽快free释放。(堆内存对程序就好像公共图书馆对于人);数据段对于程序来说,对应C程序中的全局变量和静态局部变量。
(3)如果我需要一段内存来存储数据,我究竟应该把这个数据存储在哪里?(或者说我要定义一个变量,我究竟应该定义为局部变量还是全局变量还是用malloc来实现)。不同的存储方式有不同的特点,简单总结如下:
**函数内部临时使用,出了函数就不会用到,就定义局部变量;
**堆内存和数据段几乎拥有完全相同的属性,大部分时候是可以完全替换的。但是生命周期不一样。堆内存的生命周期是从malloc开始到free结束,而全局变量是从整个程序一开始执行就开始,直到整个程序结束才会消灭,伴随程序运行的一生。如果你这个变量只是在程序的一个阶段有用,用完就不用了,就适合用堆内存;如果这个变量本身和这个程序是一生相伴的,那就适合用全局变量。(堆内存就好像是租房、数据段就好像买房。堆内存就好像图书馆借书,数据段就好像自己书店买书),你以后会慢慢发现:买不如租,堆内存的使用比全局变量广泛。

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

char  str[] = "linux";		//第二种方法:定义成全局变量,放在数据段 
int main(void)
{	
	char  a[] = "linux";	//第一种方法:定义成局部变量,放在栈上;	
	char  *p = (char *)malloc(10);	
	if(NULL == p)	
	{		
		printf("malloc  error\n");		
		return -1;	
	}	
	memset(p,0,10);	
	strcpy(p,"linux");	//第三种方法:放在malloc申请的堆内存中		
	printf("%s\n",a);	
	printf("%s\n",str);	
	printf("%s\n",p);		
	printf("%p\n",a);	
	printf("%p\n",str);	
	printf("%p\n",p);		
	free(p);	
	p = NULL;	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

非主流的豆瓣

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值