C++程序的五大内存分区实例详解
C++程序在运行时所占用的内存区域,一般可分为栈内存区、堆内存区、全局/静态内存区、文字常量内存区及程序代码区5大分区:
下面使用日常开发中的编程实例,详细介绍一下这5个分区,以便大家能更深刻的理解这5大内存分区。
1、栈内存区
栈内存区是我们用的最多的分区,只要有函数的地方都会使用到这个分区。栈分区是用来存放函数参数及函数局部变量值的内存区,是由编译器在编译时自动分配和释放的。
函数中的参数与函数中的局部变量占用的内存是代码执行到函数(进入函数)是分配的,在离开时函数时这些内存会自动被释放。下面从下面几个简单的实例来更进一步地认识栈内存。
1.1、调用函数时通过栈来传递函数的参数值
调用函数时时通过栈传递参数值的,即在调用函数之前要将函数的参数值依次压入到栈上,然后再去call被调用函数的。这点从汇编代码上可以清晰地看出来。比如下面一段简单的实现两数相加的代码:
// 被调用函数 int AddNum(int a, int b) { int nSum = a + b; return nSum; } // 调用内调用函数的实例代码 int a = 7; int b = 8; int nSum = AddNum(a, b);
可以在VS中查看上述C++代码对应的汇编代码。具体的做法是,将上述代码拷贝到VS中,启动VS调试,在鼠标右键单击显示的右键菜单中点击“转到反汇编”区查看C++代码对应的汇编代码:
从上述汇编代码可以看出,在调用AddNum函数之前,将要传入的参数a和参数b的值先压到栈上,然后再去call AddNum函数。作为被调函数的AddNum会从栈上读取传入的参数内容。
1.2、线程占用的栈内存是有上限的
线程占用的栈内存是有上限的,可以在创建线程时指定栈空间的大小。在Windows上,线程默认的栈空间是1MB。线程在某一时刻的函数调用堆栈中的所有函数占用的栈空间总和,就是当前时刻的线程占用的栈内存。
进入函数时会将该函数的栈空间累计到所在线程的栈空间占用内存数上(函数内部申请存放局部变量的栈空间),离开函数则会释放它占用的栈空间,就会将所在线程占用的栈内存数上减掉函该函数占用的空间。如果当前线程占用的栈空间大于线程的上限时(一般是在进入一个函数时触发),则会报出“stack overflow”的栈溢出异常:
程序会发生崩溃。
这里有一点需要说明一下,在某个函数中使用了switch...case语句,语句中包含了多个case分支,在这些分支中定义了一些局部变量,虽然这些局部变量的生命周期只位于case子句中,但是都是直接算在所在函数的栈空间上的,是刚执行进函数就分配好了,即便当前还没运行到对应的case子句中,即便这些case子句的局部变量的生命周期仅在case子句内!
2、堆内存区
堆内存也是我们最常用的内存区,因为每个线程的栈内存是有限的,我们一般将大部分数据要放置在堆内存中。
在C++中,malloc/new申请的内存都是从堆内存上分配的,用完后由free/delete区释放的。如果没有释放堆内存,则程序结束时由操作系统统一回收。
堆内存的管理比栈内存要复杂的多,如果是堆内存异常导致的崩溃,比栈内存异常(比如内存越界引起内存访问为例)导致的崩溃,要难查的多。
如果malloc/new来的内存在用完后没有释放,则会导致内存泄露,如果频繁执行的代码中有内存泄露则是致命的,因为随着程序的运行时间的加长,会产生越来越多的内存泄露,如果将所属进程虚拟内存耗尽,会产生“Out of memory”的异常:
程序直接闪退崩溃。
3、全局/静态内存区
全局变量和静态变量的内存就是在该区上分配的,全局变量和静态变量的生命周期也是一样的,都是在程序启动时分配内存的,在程序退出时释放内存的。
全局变量一般会使用extern关键字来声明,比如:
extern int m_nClientId;
而静态变量则是使用static关键字来声明:
static int nCount;
全局变量和静态变量都要求在定义的时候要初始化,注意此处讲的定义是和声明是相对应的概念。全局变量和静态变量的区别在于,全局变量的作用域更广,整个模块中都能使用。静态变量则因其定义的位置不同有不同的作用域。
可以在函数中定义静态变量,也可以在类中定义静态成员变量。函数中定义的静态变量只能在函数中被访问,类中定义的静态变量则可以在类外部使用“类名::静态成员变量名”去访问。
4、文字常量区
该分区是用来存放常量值,如常量字符串等,比如如下的字符串常量:(将字符串常量的地址赋值给指针p):
char* p = ”this is a test.”;
该字符串占用的内存地址就是文字常量区内存上的。
该部分内存中的内容是固定的常量,是不允许修改的,程序结束后由操作系统统一回收。这部分内容比较简单,没什么要讲的。
5、程序代码区
前面说的内存都是数据段的内存,是用来存放程序运行中的各种数据的;该部分的内存是代码段的内存,是用来存放程序二进制代码的。
数据段的内存地址和代码段指令的地址,完全是两个概念,不能混为一谈。比如某个变量的内存地址是数据段的地址:
某条汇编指令的地址,则是代码段的地址:
是两个完全不搭嘎的地址,一定要区分开来。