5_数组&字符串&结构体&共用体&枚举

程序中内存从哪里来

  1. 程序执行需要内存支持

    对程序来说,内存就是程序的立足之地(程序是被放在内存中运行的);

    程序运行时需要内存来存储一些临时变量

  2. 内存管理最终是由操作系统完成的

    • 内存本身在物理上是一个硬件器件,由硬件系统提供。

    • 内存是由操作系统统一管理。

      为了内存管理方便又合理,操作系统提供了多种机制来让我们应用程序使用内存。

      这些机制彼此不同,各自有各自的特点,我们程序根据自己的实际情况来选择某种方式获取内存使用内存释放内存

      获取内存:在操作系统处登记这块内存的临时使用权限

      使用内存

      释放内存:向操作系统归还这块内存的使用权限

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

    在一个C语言程序中,能够获取的内存就是三种情况:(stack)、(heap)、数据区(.data)

  4. 栈的详解

    • 运行时自动分配&自动回收:

      栈是自动管理的,程序员不需要手工干预。方便简单。

    • 反复使用:

      栈内存在程序中其实就是那一块空间,程序反复使用这一块空间。

    • 脏内存:

      栈内存由于反复使用,每次使用后程序不会去清理,因此分配到时保留原来的值。

    • 临时性:

      函数不能返回栈变量的指针,因为这个空间是临时的

    • 栈会溢出:

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

#include <stdio.h>

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

void func2(void)
{
	int a = 33;
	int b = 33;
	int c = 33;
	printf("in func2, &a = %p\n", &a);
}

void stack_overflow(void)
{
	int a[10000000] = {0};
	a[10000000-1] = 12;
}

void stack_overflow2(void)
{
	int a = 2;
	stack_overflow2();
}


int main(void)
{
	//stack_overflow();
	stack_overflow2();
/*
	int *p = NULL;
	p = func();
	func2();
	func2();
	printf("p = %p\n", p);
	
	printf("*p = %d.\n", *p); 		// 证明栈内存完了后是脏的
*/	
	return 0;
}

程序中内存从哪里来2

  1. 堆内存详解

    • 操作系统堆管理器管理:

      堆管理器是操作系统的一个模块堆管理内存分配灵活,按需分配

    • 大块内存:

      堆内存管理着总量很大的操作系统内存块,各进程可以按需申请使用,使用完释放

    • 程序手动申请&释放:

      手工意思是需要写代码去申请malloc和释放free

    • 脏内存:

      堆内存也是反复使用的,而且使用者用完释放前不会清除,因此也是脏的。

    • 临时性:

      堆内存只在malloc和free之间属于我这个进程,而可以访问

      在malloc之前和free之后都不能再访问,否则会有不可预料的后果。

  2. 堆内存使用范例

    void *是个指针类型,*malloc返回的是一个void 类型的指针,实质上malloc返回的是堆管理器分配给我本次申请的那段内存空间的首地址(malloc返回的值其实是一个数字,这个数字表示一个内存地址)。

    • 为什么要使用void *作为类型?

      主要原因是malloc帮我们分配内存时只是分配了内存空间,至于这段空间将来用来存储什么类型的元素malloc是不关心的,由我们程序自己来决定。

    • 什么是void类型。

      早期被翻译成空型,这个翻译非常不好,会误导人。

      void类型不表示没有类型,而表示万能类型

      void的意思就是说这个数据的类型当前是不确定的,在需要的时候可以再去指定它的具体类型。

      void *类型是一个指针类型,这个指针本身占4个字节,但是指针指向的类型是不确定的,

      换句话说这个指针在需要的时候可以被强制转化成其他任何一种确定类型的指针,也就是说这个指针可以指向任何类型的元素。

    • malloc的返回值:

      成功申请空间后返回这个内存空间的指针,申请失败时返回NULL

      所以malloc获取的内存指针使用前一定要先检验是否为NULL

    • malloc申请的内存时用完后要free释放。

      free(p)会告诉堆管理器这段内存我用完了你可以回收了。

      堆管理器回收了这段内存后这段内存当前进程就不应该再使用了。因为释放后堆管理器就可能把这段内存再次分配给别的进程,所以你就不能再使用了。

    在调用free归还这段内存之前,指向这段内存的指针p一定不能丢(也就是不能给p另外赋值)。

    因为p一旦丢失这段malloc来的内存就永远的丢失了(内存泄漏),

    直到当前程序结束时操作系统才会回收这段内存

  3. malloc的一些细节表现

    malloc(0)

    malloc申请0字节内存本身就是一件无厘头事情,一般不会碰到这个需要。

    如果真的malloc(0)返回的是NULL还是一个有效指针?

    答案是:课程实践发现实际分配了16Byte的一段内存并且返回了这段内存的地址。这个答案不是确定的,因为C语言并没有明确规定malloc(0)时的表现,由各malloc函数库的实现者来定义。

    malloc(4)
    gcc中的malloc默认最小是以16Byte为分配单位的

    如果malloc小于16B的大小时都会返回一个16字节的大小的内存。malloc实现时没有实现任意自己的分配而是允许一些大小的块内存的分配。

    malloc(20)去访问第25、第250、第2500····会怎么样

    实战中:120字节处正确,1200字节处正确····终于继续往后访问总有一个数字处开始段错误了。

#include <stdio.h>
#include <stdlib.h>
 
int main(void)
{
	int *p = (int *)malloc(20);
	// 第二步:检验分配是否成功
	if (NULL == p)
	{
		printf("malloc error.\n");
		return -1;
	}
	
	*(p+3) = 12;
	*(p+300000) = 1234;
	printf("*(p+3) = %d.\n", *(p+3));
	printf("*(p+300000) = %d.\n", *(p+300000));		

/*
	int *p1 = (int *)malloc(4);		// p2-p1 = 0x10 = 16Byte
	int *p2 = (int *)malloc(4);

	printf("p1 = %p.\n", p1);		// p2-p1 = 0x10 = 16Byte
	printf("p2 = %p.\n", p2);
*/
	
/*
	int *p1 = (int *)malloc(0);
	int *p2 = (int *)malloc(0);

	printf("p1 = %p.\n", p1);		// p2-p1 = 0x10 = 16Byte
	printf("p2 = %p.\n", p2);
	*/
/*
	// 需要一个1000个int类型元素的数组
	
	// 第一步:申请和绑定
	int *p = (int *)malloc(1000*sizeof(int));
	// 第二步:检验分配是否成功
	if (NULL == p)
	{
		printf("malloc error.\n");
		return -1;
	}
	
	// 第三步:使用申请到的内存
	//p = NULL;
	//p = &a;			// 如果在free之前给p另外赋值,那么malloc申请的那段内存就丢失掉了
					// malloc后p和返回的内存相绑定,p是那段内存在当前进程的唯一联系人
					// 如果p没有free之前就丢了,那么这段内存就永远丢了。丢了的概念就是
					// 在操作系统的堆管理器中这段内存是当前进程拿着的,但是你也用不了
					// 所以你想申请新的内存来替换使用,这就叫程序“吃内存”,学名叫内存泄漏
	*(p+0) = 1;
	*(p+1) = 2;
	printf("*(p+0) = %d.\n", *(p+0));
	printf("*(p+1) = %d.\n", *(p+1));				
					
	*(p+222) = 133;
	*(p+223) = 222;				
					
	// 第四步:释放
	free(p);
	p = NULL;
	
	printf("*(p+222) = %d.\n", *(p+222));
	printf("*(p+223) = %d.\n", *(p+223));
*/
	return 0;
}

程序中内存从哪里来3

  1. 代码段、数据段、bss段

    编译器在编译程序的时候,将程序中的所有的元素分成了一些组成部分,各部分构成一个段,

    所以说段是可执行程序的组成部分

    • 代码段:

      代码段就是程序中的可执行部分,直观理解代码段就是函数堆叠组成的

    • 数据段(也被称为数据区、静态数据区、静态区):

      数据段就是程序中的数据,直观理解就是C语言程序中的全局变量。

      注意:全局变量才算是程序的数据局部变量不算程序的数据,只能算是函数的数据

    • bss段(又叫ZI(zero initial)段):

      bss段的特点就是被初始化为0

      bss段本质上也是属于数据段,bss段就是被初始化为0的数据段

    注意区分:数据段(.data)和bss段的区别和联系:

    二者本来没有本质区别,都是用来存放C程序中的全局变量的。

    区别在于把显示初始化为非零的全局变量存在.data段中

    而把显式初始化为0或者并未显式初始化的全局变量存在bss段。

    未显式初始化:C语言规定未显式初始化的全局变量值默认为0

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

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

    常量字符串在C语言中被分配到代码段

    修改时会

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值