C 程序在内存中的布局 [李园7舍_404]

笔记题目比较高级,相信还有很多地方没有笔记清楚,主要是理思路类型,为具体抽象出C程序的每个部分与内存之间的关系图,也没有找到相关书籍查看到内存每个区域(静态区(代码段,只读(代码,数据),读写段),常量区,堆,栈)的具体分布及大小。路漫漫,继续进步吧。要是有本相关的书籍看看实在是好极了。

 

1 硬件平台

软件的运行基于操作系统,操作系统基于特定的硬件结构。

 

一个硬件平台包含“处理器(CPU)”、“系统内存”、“输入、输出设备”,组成每个部分的硬件结构也都比较复杂。处理器与它们的联系依靠“控制总线”、“地址总线”及“数据总线”实现。如下图所示:

 

由图可见,处理器(processor)和内存(system memory只是硬件平台的一部分。

 

2 程序的存储

 

(1) 内存与其它概念的区别

主要与寄存器,硬盘等外部存储器区别。

 

[1] 寄存器

寄存器是为了解决访问内存数据的复杂性和耗时性而被集成在处理器内的存储单元。处理器内(以IA-32为例)包含“通用寄存器”、“段寄存器”、“指令指针寄存器”、“浮点数据寄存器”、“控制寄存器”、“调试寄存器”、“标志寄存器”。

 

[2]硬盘

硬盘是计算机的外部存储器,是计算机上常见的CDE盘。用来保存各种类型的文件(如操作系统及操作系统的安装包)。硬盘不是内存。

 

[3]内存

内存是计算机的内部存储器,也叫做主存储器。是硬件平台的一个重要部分,其作用是用于暂时存放CPU中的运算数据以及与硬盘等外部存储器交换的数据

 

内存一般采用半导体存储单元,包括随机存储器(RAM),只读存储器(ROM),以及高速缓存(CACHE)。只不过RAM是其中最重要的存储器。计算机裸机自带的程序存储在内存中的ROM部分,应用程序运行时被载入到RAM中(所有的应用程序只有被载入到内存中后才能运行,如word安装包中的可执行文件要被载入内存中后才能按照word源代码指令运行,从而才有呈现在用户面前的word交互界面)。所以跟用户打交道的多为RAM。

 

ROM

由于ROM不会因为掉电而丢失其中存储的内容,内存中的ROM部分主要用来存储计算机的固定程序和数据,如BIOS。在装操作系统之前就可以开机接入BIOS界面中。由厂家往内存中的ROM部分烧录BIOS固定程序。但随着ROM的发展,依据ROM的子系列的新功能,BIOS也不是完全的不可改写,可以通过特殊方法将BIOS升级即重新向ROM中载入新版本的BIOS

 

RAM

对于应用程序如对于一个C语言程序来说,通常所说的内存就是指RAM[ ROM不可更改,由厂家设定,一般不与ROM交道 ],此段内存具有随机读写的特性。当然随着时间的流逝,科学的进步,现在的高档内存条都是采用RAM的子系列。

一个存在硬盘上的可执行文件经双击后其内数据就会被载入内存中,从而使可执行文件开始运行。此时计算机一但关机后,载入到内存中的可执行文件数据就会丢失。在计算机开机后,还需要重新双击可执行文件载入各种数据到内存中从头开始运行。

 

CACHE

RAM由于容量大、寻址系统繁多、读写电路复杂等原因,造成了内存的工作速度大大低于CPU的工作速度,直接影响了计算机的性能。为了解决内存与CPU工作速度上的矛盾,计算机专家在CPU和内存之间增设一级容量不大、但速度很高的高速缓冲存储器(Cache)。Cache通常由静态存储器(SRAM)构成。Cache中存放常用的程序和数据,当CPU访问这些程序和数据时,首先从高速缓存中查找,如果所需程序和数据不在Cache中,则到内存中读取数据,同时将数据写到Cache中。采用Cache可以提高系统的运行速度。

 

(2)程序的存储

对于应用程序执行被载入内存中时,是被载入到内存中的RAM部分。RAM由静态区和动态区组成。静态区分为只读数据区,初始化数据区,未初始化数据区。动态区分为堆(heap)和栈(stack)。可执行程序运行被载入内存中时,会将对应的部分存储到相应的内存模块中去。

 

[1] 静态内存区

静态内存主要用来存储“程序代码”及“数据”。

 

最终的可执行文件的代码部分会被存储在RAM中的某区域,存储指令的内存区域可将其称为代码段。代码段的指令由CS值存储基址,由指令指针寄存器指向下一条要取的指令。

 

在静态区中存储的数据主要指自动全局变量和静态变量。在C语言中,经初始化的全局变量和静态变量存储在静态区的一块区域中(将其称为数据段),未经初始化的全局变量和静态变量存储在静态区的另一块内存中(可将其称之为BSSBSS是英文Block Started by Symbol的简称,表示此种变量不在可执行文件中占用内存,用变量名作占位符记录此类型变量需要多少内存空间,系统将其初始化为0)。

 

对于静态局部变量,对局部变量加了static关键字后就改变了局部变量的存储方式。静态局部变量就不会再存储在栈上而是被存储在RAM中的静态区。

 

[2] 动态内存区

动态内存主要是在程序运行时且运行完分配动态内存的指令后动态内存才被分配。

 

对于来说,栈专门用来存非静态的局部变量和调用子函数时传递给子函数的实参。当非静态局部变量的生存期完后,栈内存也随之被系统回收。

 

对于来说,程序代码中有动态分配堆内存的函数(malloc/new)出现才有可能分配堆空间。并使用完堆内存后要用释放动态内存函数(free/delete)释放。即堆空间的分配和回收都是通过特定的代码实现。不管是分配还是释放环节,稍有不慎就会有内存泄露的危险。

 

[3] 常量区

就算是没有这个区域也可以这么看。可以将程序中突如其来的常量的存储区域称作常量区

 

[4] 程序

常量区和栈
#include <stdio.h>

char *mm_address();
char *stack_address();

int main(void)
{
        char *p = NULL;

        p       = mm_address();
        if(p){
                printf("mm address content: %s\n", p);
                p       = NULL;
        }

        p       = stack_address();
        if(p){
                printf("stack address content: %s\n", p);
                p       = NULL;
        }
        return 0;
}

//Return const mm address
char *mm_address()
{
        char *pC = "Hello World!\n";
        return pC;
}

//Retrun stack address
char *stack_address()
{
        char ar[] = "Hello World!\n";
        return ar;
}

 

编译并执行程序得到如下结果:

  • mm_address()函数能将字符串的地址返回给main函数中的p。在main程序中p所指的地址内还有正确的内容,说明“char *pC = "Hello World!\n";”中的字符串常量绝对不是存在了栈上。这样的字符串常量就被存在RAM的常量区。从程序执行结果可知,常量区数据的生命期至少不必main函数小,因为在main函数中字符串常量的内存还没有被释放掉。
  • stack_address()能够将字符串常量的地址返回给main函数中的p(p不为空)。但输出的是乱码。说明p所指内存的内容已经不存在。其实“char ar[] = "Hello World!\n";”语句是在栈上分配了以ar为名(首)的连续空间。等子函数运行完毕后,曾经存储字符串的占空间已经被回收。故而输出乱码。
堆和栈
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
        char *p;
        p       = NULL;

        p       = (char *)malloc(sizeof(char) * 10);
        if(p){
                *(p + 1)        = 'h';
                printf("the seconde byte content: %c\n", *(p+1));
        }

        if(p){
                free(p);
                p       = NULL;
        }
        return 0;
}
  • 程序运行到“char *p;”语句时,由于p为局部变量,p指针变量的内存在栈上
  • p       = (char *)malloc(sizeof(char) * 10);”语句欲分配大小为10字节的堆空间,分配成功后堆空间的首地址由p存储。堆空间的首地址不容弄掉。
  • 接下来往堆空间的第二个字节内存入’h’字符并将其打印出来,这些都是操作堆空间的行为。
  • 若堆空间分配成功后,手动将其释放掉。free(p)只是表示将堆空间回收,但p原本指向堆空间的值还存在,故而需要将p重新赋值为NULL,以避免使用野指针。

 

其它

对于其它前辈所标识的程序中的何种数据位于RAM中的哪个区,在不需要高度把握整个程序的运行情况下,似乎作用不是很大。

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

int a = 0; //全局初始化区  
char *p1; //全局未初始化区  

int main(void)  
{  
	int b;// 栈  
	char s[] = "abc"; //栈  
	char *p2; //栈  
	char *p3 = "123456"; //"123456/0"在常量区,p3在栈上。  
	static int c =0; //全局(静态)初始化区  
	p1 = (char *)malloc(10);  
	p2 = (char *)malloc(20);  //分配得来得10和20字节的区域就在堆区。  
	strcpy(p1, "123456"); //123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。  
}  


 

不禁猜之,内存硬件设计者肯定将内存划分了区域。如存储引导程序BIOS的ROM地址范围为0xyyyyyyyy ~ 0xzzzzzzzz。RAM代码段的地址范围为0xmmmmmmmm ~ 0xnnnnnnnn……清楚内存每个区域的范围,也就更加深入的了解了内存,可以直接对内存编写代码(不知道危险与否)。也知道了每个区域的内存大小,也就知道每种类型的数据最多能够定义多少,对编写程序实在是有帮助,尤其是对编写到嵌入式、单片机之类的程序。大师一样。继续探知。

 

此次笔记记录完毕。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值