C++ 内存分布

C++内存分布

参考链接http://www.cnblogs.com/skynet/archive/2011/03/07/1975479.html#

作者 吴秦

为什么需要知道C/C++的内存布局和在哪可以可以找到想要的数据?知道内存布局对调试程序非常有帮助,可以知道程序执行时,到底做了什么,有助于写出干净的代码。本文的主要内容如下:

  • 源文件转换为可执行文件
  • 可执行程序组成及内存布局
  • 数据存储类别
  • 一个实例
  • 总结

源文件转换为可执行文件

源文件经过以下几步生成可执行文件:

  • 1、预处理(preprocessor):对#include、#define、#ifdef/#endif、#ifndef/#endif等进行处理
  • 2、编译(compiler):将源码编译为汇编代码
  • 3、汇编(assembler):将汇编代码汇编为目标代码
  • 4、链接(linker):将目标代码链接为可执行文件

编译器和汇编器创建的目标文件包含:二进制代码(指令)、源码中的数据;链接器将多个目标文件链接成一个;装载器吧目标文件加载到内存。

图1 源文件到可执行文件的步骤

可执行文件组成及内存布局

通过上面的小节,我们知道将源程序转换为可执行程序的步骤,典型的可执行文件分为两部分:

  • 代码段(Code),由机器指令组成,该部分是不可改的,编译之后就不再改变,放置在文本段(.text)。
  • 数据段(Data),它由以下几部分组:
    • 常量(constant),通常放置在只读read-only的文本段(.text)
    • 静态数据(static data),初始化的放置在数据段(.data);未初始化的放置在(.bss,Block Started by Symbol,BSS段的变量只有名称和大小却没有值)
    • 动态数据(dynamic data),这些数据存储在堆(heap)或栈(stack)

源程序编译后链接到一个以0地址为始地址的线性或多维虚拟地址空间。而且每个进程都拥有这样一个空间,每个指令和数据都在这个虚拟地址空间拥有确定的地址,把这个地址称为虚拟地址(Virtual Address)。将进程中的目标代码、数据等虚拟地址组成的虚拟空间称为虚拟存储器(Virtual Memory)。典型的虚拟存储器中有类似的布局:

  • Text Segment (.text)
  • Initialized Data Segment (.data)
  • Uninitialized Data Segment (.bss)
  • The Stack
  • The Heap

如下图所示:

图2 进程内存布局

当进程被创建时,内核为其提供一块物理内存,将虚拟内存映射到物理内存,这些都是由操作系统来做的。

数据存储类别

讨论C/C++中的内存布局,不得不提的是数据的存储类别!数据在内存中的位置取决于它的存储类别。一个对象是内存的一个位置,解析这个对象依赖于两个属性:存储类别、数据类型。

  • 存储类别决定对象在内存中的生命周期。
  • 数据类型决定对象值的意义,在内存中占多大空间。

C/C++中由(auto、 extern、 register、 static)存储类别和对象声明的上下文决定它的存储类别。

自动对象(automatic objects)

auto和register将声明的对象指定为自动存储类别。他们的作用域是局部的,诸如一个函数内,一个代码块{***}内等。操作了作用域,对象会被销毁。

  • 在一个代码块中声明一个对象,如果没有执行auto,那么默认是自动存储类别。
  • 声明为register的对象是自动存储类别,存储在计算机的快速寄存器中。不可以对register对象做取值操作“&”,而且不能是静态的。

静态对象(static objects)

静态对象可以局部的,也可以是全局的。静态对象一直保持它的值,例如进入一个函数,函数中的静态对象仍保持上次调用时的值。包含静态对象的函数不是线程安全的、不可重入的,正是因为它具有“记忆”功能。

  • 局部对象声明为静态之后,将改变它在内存中保存的位置,由动态数据--->静态数据,即从堆或栈变为数据段或bbs段。
  • 全局对象声明为静态之后,而不会改变它在内存中保存的位置,仍然是在数据段或bbs段。但是static将改变它的作用域,即该对象仅在本源文件有效。此相反的关键字是extern,使用extern修饰或者什么都不带的全局对象的作用域是整个程序。

一个实例

下面我们分析一段代码:


1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

#include
<stdio.h>

#include
<stdlib.h>

int a;

static int b;

void func(
void )

{

    char c;

    static int d;

}

int main(
void )

{

    int e;

    int *pi
= (
int *)
malloc (
sizeof (
int ));

    func
();

    func
();

    free (pi
);

    return (0);

}

程序中声明的变量a、b、c、d、e、pi的存储类别和生命期如下所述:

  • a是一个未初始化的全局变量,作用域为整个程序,生命期是整个程序运行期间,在内存的bbs段
  • b是一个未初始化的静态全局变量,作用域为本源文件,生命期是整个程序运行期间,在内存的bbs段
  • c是一个未初始化的局部变量,作用域为函数func体内,即仅在函数体内可见,生命期也是函数体内,在内存的栈中
  • d是一个未初始化的静态局部变量,作用域为函数func体内,即仅在函数体内可见,生命期是整个程序运行期间,在内存的bbs段
  • e是一个未初始化的局部变量,作用域为函数main体内,即仅在函数体内可见,生命期是main函数内,在内存的栈中
  • pi是一个局部指针,指向堆中的一块内存块,该块的大小为sizeof(int),pi本身存储在内存的栈中,生命期是main函数内
  • 新申请的内存块在堆中,生命期是malloc/free之间

用图表示如下:

图3 例子的内存布局

总结

本文介绍了C/C++中由源程序到可执行文件的步骤,和可执行程序的内存布局,数据存储类别,最后还通过一个例子来说明。可执行程序中的变量在内存中的布局可以总结为如下:

  • 变量(函数外):如果未初始化,则存放在BSS段;否则存放在data段
  • 变量(函数内):如果没有指定static修饰符,则存放在栈中;否则同上
  • 常量:存放在文本段.text
  • 函数参数:存放在栈或寄存器中

内存可以分为以下几段:

  • 文本段:包含实际要执行的代码(机器指令)和常量。它通常是共享的,多个实例之间共享文本段。文本段是不可修改的。
  • 初始化数据段:包含程序已经初始化的全局变量,.data。
  • 未初始化数据段:包含程序未初始化的全局变量,.bbs。该段中的变量在执行之前初始化为0或NULL。
  • 栈:由系统管理,由高地址向低地址扩展。
  • 堆:动态内存,由用户管理。通过malloc/alloc/realloc、new/new[]申请空间,通过free、delete/delete[]释放所申请的空间。由低地址想高地址扩展。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-25 09:04:05

C++ 内存分布的相关文章

C++类的内存分布

使用Visual Studio工具来看是类的内存分布 先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1 reportSingleClassLayoutXXX(XXX为类名),则只会打出指定类XXX的内存布局.近期的VS版本都支持这样配置. 下面可以定义一个类,像下面这样: class Base { int a; int b; public: void CommonFunction(); };

[转]C++类内存分布

转自:http://www.cnblogs.com/jerry19880126/p/3616999.html 书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别是在继承.虚函数存在的情况下. 工欲善其事,必先利其器,我们先用好Visual Studio工具,像下面这样一步一步来: 先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1

C++对象内存分布(2) - 菱形继承(non virtual)

1.前言 本篇文章的所有代码例子,如果是windows上编译运行,则使用的是visual studio 2013.如果是RHEL6.5平台(linux kernal: 2.6.32-431.el6.i686)上编译运行,则其gcc版本为4.4.7,如下所示: [[email protected] ~]# gcc --version gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4) 2.菱形继承类的内存分布 2.1.类的结构 菱形继承 - 重复继承 2.2.实现

《C++反编译与逆向分析技术揭秘》之学习笔记02--结构体和类之内存分布

※结构体和类之内存分布 1.空类的大小空类:其实空类至少会占用1个字节的长度. 2.字节对齐在为结构体和类中的数据成员分配内存时,结构体中的当前数据成员类型长度为M,指定对齐值为N,那么实际对齐值位q=min(M,N),其成员的地址安排在q的倍数上. vc6.0缺省对齐8个字节sShort占用2个字节,所以安排的地址0x0012FF70为2的倍数.nInt占用4个字节,所以安排的地址0x0012FF74为4的倍数.因为结构体中最大的字段长度为4,所以对齐值调整为4个字节.因为test对象为8个字

jvm堆内存分布及gc发生的条件

jvm虚拟机对内存管理主要体现在堆内存的管理上,我们可以在启动jvm的时候设置jvm对内存大小及调整策略. 1.jvm启动参数: -Xms:jvm启动时初始堆大小. -Xmx:jvm堆的最大值. -Xss:线程栈大小. -Dname=value:jvm全局属性设置. jvm启动参数设置有很多,以上只是列举本人接触过的几个参数. 1)首先,-Xms是jvm启动时堆内存的初始大小,当堆内存不够用时,jvm调整堆大小到-Xmx设置的大小.一般resin这些服务器会把-Xms和-Xmx大小设置一样以避免

使用汇编分析c代码的内存分布

arm平台下使用反汇编分析c内存分布: arm:使用arm-linux-objdump命令将编译完成之后的elf文件,进行反汇编. 之后重定向到tmp.s文件中. 第一步变量如下c文件. vim tmp.c #include<stdio.h> #define VAR 0xFF int a = 0; static int b = 0; int c = 10; static int d = 20; const int finalone = 10; const int final; int main

C++类内存分布

转载:http://www.cnblogs.com/jerry19880126/p/3616999.html 书上类继承相关章节到这里就结束了,这里不妨说下C++内存分布结构,我们来看看编译器是怎么处理类成员内存分布的,特别是在继承.虚函数存在的情况下. 工欲善其事,必先利其器,我们先用好Visual Studio工具,像下面这样一步一步来: 先选择左侧的C/C++->命令行,然后在其他选项这里写上/d1 reportAllClassLayout,它可以看到所有相关类的内存布局,如果写上/d1

深入探索C++对象象模型--拷贝构造函数 &amp;&amp;多重继承 虚拟继承 内存分布

拷贝构造函数 如果没有定义拷贝构造函数,那么编译器会自动生成一个拷贝构造函数,但是这个拷贝构造函数是有一定限度的. 一般来说这个拷贝构造函数是按照位直接拷贝的,但是在有些情况下这种初始化是有问题的,在特殊的四种情况下是有问题的,在有问题的情况下,可以举例说明. 如果一个有多态性质的对象,子类赋值给父类,调用了拷贝构造函数,这个时候就需要给父类的虚拟函数表重新分配,使得虚拟函数表和子类不是同一个,这样bitwist就不能有效 对于在函数参数中调用拷贝构造函数,参数是实参的一根拷贝,对于函数的返回值

C++对象内存分布(3) - 菱形继承(virtual)

1.前言 本篇文章的所有代码例子,如果是windows上编译运行,则使用的是visual studio 2013.如果是RHEL6.5平台(linux kernal: 2.6.32-431.el6.i686)上编译运行,则其gcc版本为4.4.7,如下所示: [[email protected] ~]# gcc --version gcc (GCC) 4.4.7 20120313 (Red Hat 4.4.7-4) 2.菱形继承类的内存分布 本篇文章主要讨论的是虚继承(virtual)下的内存分

C++ 继承之虚继承与普通继承的内存分布

仅供互相学习,请勿喷,有观点欢迎指出~ class A { virtual void aa(){}; }; class B : public virtual A { char j[3]; //加入一个变量是为了看清楚class中的vfptr放在什么位置 public: virtual void bb(){}; }; class C : public virtual A { char i[3]; public: virtual void cc(){}; }; class C1 : public A