C++程序在内存中的布局详细分析篇

笔者我是初学者,所以文章中有出错的地方请不吝指教。我是个比较喜欢较真的人,或者说是个一探究竟的人,即知其然更知其所以然的道理。

大学学习数据结构的时候知道了一个经典的定义:程序=算法+数据(数据的组织形式、数据结构)。然后具体到机器层面,就是CPU执行内存中的操作指令(也可以称作代码),指令当然是一些二进制数字,指令大多是操作数据的指令,数据当然也存在内存中。这篇讲的是C++语言的 ,我们写C++语言的时候,会定义一些数据变量,然后对这些数据进行操作,当然编译器会把C++语言编译成机器操作指令,所以程序最终的形式是操作指令(代码)和数据分别来存放在内存中。有人会疑问,操作指令要操作数据的时候,要根据数据在内存中的地址找到数据,为什么操作指令后面不直接跟着操作数?这是因为我们可能定义的是一个在随时变动的数据,操作指令不能确定这个操作数是多少,其实这个涉及到常量问题,当操作数是常量的时候,其实往往操作指令后面跟的就是数据了,而不是数据在内存的地址,这个其实就是常量折叠的概念(后面再讲)。

C++是支持分段编译的,即我可以在很多CPP文件中写程序,最后放在一起编译,这个就涉及到了一个问题,定义的变量(程序中的数据)的作用域问题,当然,在函数中定义的变量的作用域肯定是函数范围内可见,C++如果不涉及到类的话,C++程序就是很多全局作用域变量(常量)、文件作用域变量(常量)和函数(函数中可能也会定义变量(常量))组成,(其中有一个主函数main。因为程序是以栈的方式运行的(这个不多讲),所以程序开始运行main函数进栈,当然main函数中会调用其它函数,其它函数也会可能调用其它函数,调用函数就是进栈,实参进栈,最后函数操作完,退栈,知道main函数完成,程序就表示运行完毕,可能main函数会输出结果。),还有一个就是比较怪异的变量——静态变量,用static修饰。在程序已经放在内存的时候,即操作指令(代码段)和数据(数据段)已经放到内存的时候,说明全局作用域变量(常量)、文件作用域变量(常量)。

变量(常量)为什么有作用域之分,主要是因为有分段编译的存在,当然也有局部作用域的问题({}括号的问题)。也就是说很多CPP文件,那么在CPP文件中定义的变量(常量)其它的文件中的函数能调用吗,这就产生了作用域的问题,当然函数内变量(常量)也有作用域,函数内定义的变量(常量)作用域仅仅是局限于函数内,函数外即不能用了。

全局作用域变量(常量),每个CPP文件中的函数都能使用,那么这种变量(常量)只能有一次定义,当然其它CPP文件中的函数要使用需要先声明(一般由头文件完成)。

文件作用域变量(常量),只是这个CPP文件中的函数能够使用,其它CPP文件内的 函数不能使用,那么我不同的CPP文件就可能存在重名的变量(常量),这是可以的。

以上两种是常见的,以上两种都是在函数之外定义的变量(常量)。那下面就写几个程序举下栗子o(∩_∩)o :第一个是file1.cpp,第二个是file2.cpp

int a=10;//初始化的全局作用域的变量
stataic int b=10;//初始化的文件作用域的变量
const int c=10;//文件作用域的常量
extern const int d=10;//全局作用域的常量

int main(){
int m=10;//局部作用域变量
const int n=10;//局部作用域常量
}

extern int a;//声明a,可以使用file1中的a变量
static int b=10;//file1和file2都有这个定义,但不会出现重复定义问题,以为加了static的修饰后变为文件作用域的变量
const int c=10;//和上面同理
extern const int d;//这样子就是声明了一个全局作用域的常量

void fun(){}

void fun1(){}

void fun2(){}

我们现在为止,介绍的是函数外定义的变量(常量)和函数内定义的变量(常量)问题,并且是从作用域的角度来看的。

接下来我们从定义的变量(常量)在内存中分布的角度来分析:C++经过编译之后一共分为五个段(占用内存中的分配形式):

栈(stack)。由编译器自动分配释放。存放函数参数和函数里的局部作用域变量(常量)。其操作方式类似于数据结构中的栈。例如,声明在函数中一个局部变量int x; 系统自动在栈中为x分配一块空间,该空间存储x的值。

堆(heap)。用于动态内存空间分配。一般由程序员进行分配和释放,若程序员不释放,程序结束时可能由操作系统回收。注意它与数据结构中的堆是两回事,分配方式类似于链表。内存分配在C中使用malloc函数,在C++中用new操作符。以下为C语言小示例:p1 = (char *)malloc(10);注意p1本身是在栈中的,而malloc(10)的数据存放在堆中,p1只是指向了这些数据。

变量区(静态区)(static)。全局作用域变量、文件作用域变量和静态变量(上面没讲,下面再讲)的存储是放在一块的,初始化的变量和静态变量在一块区域,未初始化的变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。所以有的文章介绍,这里是两个区域rdata和bbs区域。

常量区。常量字符串就是放在这里(全局区)。程序结束后由系统释放。常量区没有初始化和未初始化之分,只要定义了常量,就必须初始化。这里有个字符串常量的概念,下面再讲,这里先有个印象。

程序代码区。存放函数体的二进制代码。即操作指令存放的地方,程序执行的时候,将CPU会从这里读取指令。

所以一共5个地址区,因为变量区有初始化和未初始化之分,下面 就用程序验证存在五个地址段 存放五中不同的变量(常量):

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

using namespace std;

int a=10;//初始化的全局作用域的变量

static int b=10;//初始化的文件作用域的变量

int aa;//未初始化的全局作用域的变量

static int bb;//未初始化的文件作用域的变量

const int c=10;//文件作用域的常量

extern const int d=10;//全局作用域的常量


int main(){
	static int s=10;//静态变量
	int m=10;//局部作用域变量
	const int n=10;//局部作用域常量

	int *p=new int(1);
	int *q=new int(2);

	cout<<"栈区"<<endl;
	cout<<"m的地址:"<<&m<<endl;
	cout<<"n的地址:"<<&n<<endl;
	cout<<"p的地址:"<<&p<<endl;
	cout<<"q的地址:"<<&q<<endl;
	cout<<"//我是分割线//"<<endl;

	cout<<"堆区"<<endl;
	cout<<"p的地址:"<<p<<endl;
	cout<<"q的地址:"<<q<<endl;
	cout<<"//我是分割线//"<<endl;

	cout<<"初始化的变量区"<<endl;
	cout<<"a的地址:"<<&a<<endl;
	cout<<"b的地址:"<<&b<<endl;
	cout<<"//我是分割线//"<<endl;

	cout<<"未初始化的变量区"<<endl;
	cout<<"aa的地址:"<<&aa<<endl;
	cout<<"bb的地址:"<<&bb<<endl;
	cout<<"//我是分割线//"<<endl;

	cout<<"常量区"<<endl;
	cout<<"c的地址:"<<&c<<endl;
	cout<<"d的地址:"<<&d<<endl;
	cout<<"//我是分割线//"<<endl;



}


以上结果 就可以看出 初始化的变量区和未初始化的变量区接近。栈区的地址段、堆区的地址段、变量区的地址段和常量区的地址段明显不同,代码区的地址段暂时不会显示。

当然这些结果或者内存的分配并不是唯一的,其实这个对编译器、操作系统或者说是编程平台等是具有一定依赖性,我使用的是winxp和VC6.0,当然你可能使用不同的平台,会有不同的结果,但是对原理的分析方法都是相同的,当然这个C++程序内存分配在哪,并不会对你的编程有什么影响,但是这个可以强化你对变量常量等作用域的理解和掌握。

其实程序运行前,栈和堆没有分配内存的,只有代码区、变量区和常量区是存在,这个也好理解。这篇文章就讲这么多,关于static静态变量,const等的使用,下面几篇文章再讲,其实对这篇文章理解了,这些关键词的作用基本上也就很容易理解了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值