C++类对应的内存结构

C++类对应的内存结构
C++类对应的内存结构

C++类对应的内存结构

提示1:对“内存结构”表示有疑问或不解的,先参考:

https://www.360docs.net/doc/ed7388092.html,/guogangj/archive/2007/05/25/1625199.aspx,

本文使用的表示方法和VC6的Memory视图一致,即:左上表示低位。

提示2:下文提到的“类大小”严格上来说是该类经过实例化的对象的大小。当然了,光研究长度的话,两者差别不大,因为:CClassA objA,sizeof(CClassA)和sizeof(objA)得到的结果都是一样的。

一、真空类

class CNull

{

};

长度:1

内存结构:

??

评注:长度其实为0,这个字节作为内容没有意义,可能每次都不一样。

二、空类

class CNull2

{

public:

CNull2(){printf("Construct\n");}

~CNull2(){printf("Desctruct\n");}

void Foo(){printf("Foo\n");}

};

长度:1

内存结构:

??

评注:同真空类差不多,内部的成员函数并不会影响类大小。

三、简单类

class COneMember

{

public:

COneMember(int iValue = 0){m_iOne = iValue;};

private:

int m_iOne;

};

长度:4

内存结构:

00 00 00 00 //m_iOne

评注:成员数据才影响类大小。

四、简单继承

class CTwoMember:public COneMember

{

private:

int m_iTwo;

};

长度:8

内存结构:

00 00 00 00 //m_iOne

CC CC CC CC //m_iTwo

评注:子类成员接在父类成员之后。

五、再继承

class CThreemember:public CTwoMember

{

public:

CThreemember(int iValue=10) {m_iThree = iValue;}; private:

int m_iThree;

};

长度:12

内存结构:

00 00 00 00 //m_iOne

CC CC CC CC //m_iTwo

0A 00 00 00 //m_iThree

评注:孙类成员接在子类之后,再再继承就依此类推了。

六、多重继承

class ClassA

{

public:

ClassA(int iValue=1){m_iA = iValue;};

private:

int m_iA;

};

class ClassB

{

public:

ClassB(int iValue=2){m_iB = iValue;};

private:

int m_iB;

};

class ClassC

{

public:

ClassC(int iValue=3){m_iC = iValue;};

private:

int m_iC;

};

class CComplex :public ClassA, public ClassB, public ClassC {

public:

CComplex(int iValue=4){m_iComplex = iValue;};

private:

int m_iComplex;

};

长度:16

内存结构:

01 00 00 00 //A

02 00 00 00 //B

03 00 00 00 //C

04 00 00 00 //Complex

评注:也是父类成员先出现在前边,我想这都足够好理解。

七、复杂一些的继承

不写代码了,怕读者看了眼花,改画图。

长度:32

内存结构:

01 00 00 00 //A

02 00 00 00 //B

03 00 00 00 //C

04 00 00 00 //Complex

00 00 00 00 //OneMember

CC CC CC CC //TwoMember

0A 00 00 00 //ThreeMember

05 00 00 00 //VeryComplex

评注:还是把自己的成员放在最后。

只要没涉及到“虚”(Virtual),我想没什么难点,不巧的是“虚”正是我们要研究的内容。

八、趁热打铁,看“虚继承”

class CTwoMember:virtual public COneMember

{

private:

int m_iTwo;

};

长度:12

内存结构:

E8 2F 42 00 //指针,指向一个关于偏移量的数组,且称之虚基类偏移量表指针CC CC CC CC // m_iTwo

00 00 00 00 // m_iOne(虚基类数据成员)

评注:virtual让长度增加了4,其实是多了一个指针,关于这个指针,确实有些复杂,别的文章有具体分析,这里就不岔开具体讲了,可认为它指向一个关于虚基类偏移量的数组,偏移量是关于虚基类数据成员的偏移量。

九、“闭合”虚继承,看看效果

长度:24

内存结构:

14 30 42 00 //ClassB的虚基类偏移量表指针

02 00 00 00 //m_iB

C4 2F 42 00 //ClassC的虚基类偏移量表指针

03 00 00 00 //m_iC

04 00 00 00 //m_iComplex

01 00 00 00 //m_iA

评注:和预料中的一样,虚基类的成员m_iA只出现了一次,而且是在最后边。当然了,更复杂的情况要比这个难分析得多,但虚继承不是我们研究的重点,我们只需要知道:虚继承利用一个“虚基类偏移量表指针”来使得虚基类即使被重复继承也只会出现一次。

十、看一下关于static成员

class CStaticNull

{

public:

CStaticNull(){printf("Construct\n");}

~CStaticNull(){printf("Desctruct\n");}

static void Foo(){printf("Foo\n");}

static int m_iValue;

};

长度:1

内存结构:(同CNull2)

评注:可见static成员不会占用类的大小,static成员的存在区域为静态区,可认为它们是“全局”的,只是不提供全局的访问而已,这跟C的static其实没什么区别。

十一、带一个虚函数的空类

class CVirtualNull

{

public:

CVirtualNull(){printf("Construct\n");}

~CVirtualNull(){printf("Desctruct\n");}

virtual void Foo(){printf("Foo\n");}

};

长度:4

内存结构:

00 31 42 00 //指向虚函数表的指针(虚函数表后面简称“虚表”)

00423100:(虚表)

41 10 40 00 //指向虚函数Foo的指针

00401041:

E9 78 02 00 00 E9 C3 03 … //函数Foo的内容(看不懂)

评注:带虚函数的类长度就增加了4,这个4其实就是个指针,指向虚函数表的指针,上面这个例子中虚表只有一个函数指针,值就是“0x00401041”,指向的这个地址就是函数的入口了。

十二、继承带虚函数的类

class CVirtualDerived : public CVirtualNull

{

public:

CVirtualDerived(){m_iVD=0xFF;};

~CVirtualDerived(){};

private:

int m_iVD;

};

长度:8

内存结构:

3C 50 42 00 //虚表指针

FF 00 00 00 //m_iVD

0042503C:(虚表)

23 10 40 00 //指向虚函数Foo的指针,如果这时候创建一个CVirtualNull对

象,会发现它的虚表的内容跟这个一样

评注:由于父类带了虚函数,子类就算没有显式声明虚函数,虚表还是存在的,虚表存放的位置跟父类不同,但内容是同的,也就是对父类虚表的复制。

十三、子类有新的虚函数

class CVirtualDerived: public CVirtualNull

{

public:

CVirtualDerived(){m_iVD=0xFF;};

~CVirtualDerived(){};

virtual void Foo2(){printf("Foo2\n");};

private:

int m_iVD;

};

长度:8

内存结构:

24 61 42 00 //虚表指针

FF 00 00 00 //m_iVD

00426124:(虚表)

23 10 40 00

50 10 40 00

评注:虚表还是只有一张,不会因为增加了新的虚函数而多出另一张来,新的虚函数的指针将添加在复制了的虚表的后面。

十四、当纯虚函数(pure function)出现时

class CPureVirtual

{

virtual void Foo() = 0;

};

class CDerivePV : public CPureVirtual

{

void Foo(){printf("vd: Foo\n");};

};

长度:4(CPureVirtual),4(CDerivePV)

内存结构:

CPureVirtual:

(不可实例化)

CDerivePV:

28 50 42 00 //虚表指针

00425028:(虚表)

5A 10 40 00 //指向Foo的函数指针

评注:带纯虚函数的类不可实例化,因此列不出其“内存结构”,由其派生类实现纯虚函数。我们可以看到CDerivePV虽然没有virtual声明,但由于其父类带virtual,所以还是继承了虚表,如果CDerivePV有子类,还是这个道理。

十五、虚函数类的多重继承

前面提到:(子类的虚表)不会因为增加了新的虚函数而多出另一张来,但如果有多重继承的话情况就不是这样了。下例中你将看到两张虚表。

大小:24

内存结构

F8 50 42 00 //虚表指针

01 00 00 00 //m_iA

02 00 00 00 //m_iB

E8 50 42 00 //虚表指针

03 00 00 00 //m_iC

04 00 00 00 //m_iComplex

004250F8:(虚表)

5A 10 40 00 //FooA

55 10 40 00 //FooB

64 10 40 00 //FooComplex

004250E8:(虚表)

5F 10 40 00 //FooC

评注:子类的虚函数接在第一个基类的虚函数表的后面,所以B接在A后面,Complex接在B后面。基类依次出现,子类成员接在最后面,所以m_iComplex

位于最后面。

本来还想看看更复杂些的情况,甚至包括虚继承和虚函数同时出现的多重多层继承情况,但确实有些复杂了,自己还有些找不到规律,所以准备之后再补充。

C语言内存分布之数据段详解

在线学习好工作https://www.360docs.net/doc/ed7388092.html,/ C语言内存分布之数据段详解 不管我们以后是自己写代码还是读别人的代码,都应该想想这个变量默认存储的位置。在我们以后的嵌入式开发中,技巧性的代码越来越多的时候,我们可能把某一些代码放在一段。我们可以通过修改变量或者代码默认放置的段,让它被放到其它的段中。我们也可以自己定义一个新的段。 随着运行,栈空间是随时会变化的。栈空间临时的去存储一些变量,当我们进入一个函数,系统就会在栈空间去分配一片内存去保存这个函数里面所有的变量。当函数执行完之后,这一片内存的数据就被销毁了。当我们在初学的时候,就千万不要犯把局部变量给别人的错误。 全局的数据空间和只读数据段,代码段都是在运行之前就已经分配好了的。所以我们在打印信息的时候应该尽量不要使用太多的字符串,否则容易导致程序的可执行文件很大。所以我们在最后发布程序的release版本里面去除了我们一些调试用的信息。 测试代码如下

我们在main函数外面申请了一个全局变量b,但是我们并没有为这个b赋初始值。它仅仅只是为这个变量圈出了一块内存,里面有为全局变量自动初始化的0值。 运行结果如下 上面的结果是没有变量b的结果,下面是添加全局变量b的结果。bss段就是未初始化的全局变量存储的段,上面的全局变量b就在这个段中分配了四个字节(尽管系统已经将它初始化为0,但是并不是我们初始化的)。

测试代码如下 我们只是将未初始化的变量b将它初始化了。 运行结果如下 可以看到,在未初始化的字段里面少了四个字节,而在已初始化的字段里面多了四个字节。所以我们也将date称做全局初始化数据空间,bss称做全局未初始化数据空间。局部的变量不管有没有初始化都是存储在栈上。

程序的书写规则(程序的编码规范)

程序的书写规则(程序的编码规范) 随着软件产品的功能增加和版本的提高,代码越来越复杂,源文件也越来越多,对于软件开发人员来说,除了保证程序运行的正确性和提高代码的运行效率之外,规范风格的编码会对软件的升级、修改、维护带来极大的方便性,也保证程序员不会陷入“代码泥潭”中无法自拔。开发一个成熟的软件产品,除了有详细丰富的开发文档之外,必须在编写代码的时候就有条不紊,细致严谨。 以下的编码规范包含了程序排版、注释、命名、可读性、变量、程序效率、质量保证、代码编译、代码测试和版本控制等注意事项。 一、排版: 1.关键词和操作符之间加适当的空格。 2.相对独立的程序块与块之间加空行 3.较长的语句、表达式等要分成多行书写。 4.划分出的新行要进行适应的缩进,使排版整齐,语句可读。 5.长表达式要在低优先级操作符处划分新行,操作符放在新行之首。 6.循环、判断等语句中若有较长的表达式或语句,则要进行适应的划分。 7.若函数或过程中的参数较长,则要进行适当的划分。 8.不允许把多个短语句写在一行中,即一行只写一条语句。 9.函数或过程的开始、结构的定义及循环、判断等语句中的代码都要采用缩进风 格。 10.C/C 语言是用大括号‘{’和‘}’界定一段程序块的,编写程序块时‘{’和 ‘}’应各独占一行并且位于同一列,同时与引用它们的语句左对齐。在函数体的开始、类的定义、结构的定义、枚举的定义以及if、for、do、while、switch、case语句中的程序都要采用如上的缩进方式。

二、注释 1.注释要简单明了。 2.边写代码边注释,修改代码同时修改相应的注释,以保证注释与代码的一致性。 3.在必要的地方注释,注释量要适中。注释的内容要清楚、明了,含义准确,防 止注释二义性。保持注释与其描述的代码相邻,即注释的就近原则。 4.对代码的注释应放在其上方相邻位置,不可放在下面。 5.对数据结构的注释应放在其上方相邻位置,不可放在下面;对结构中的每个域 的注释应放在此域的右方;同一结构中不同域的注释要对齐。 6.变量、常量的注释应放在其上方相邻位置或右方。 7.全局变量要有较详细的注释,包括对其功能、取值范围、哪些函数或过程存取 它以及存取时注意事项等的说明。 8.在每个源文件的头部要有必要的注释信息,包括:文件名;版本号;作者;生 成日期;模块功能描述(如功能、主要算法、内部各部分之间的关系、该文件与其它文件关系等);主要函数或过程清单及本文件历史修改记录等。 9.在每个函数或过程的前面要有必要的注释信息,包括:函数或过程名称;功能 描述;输入、输出及返回值说明;调用关系及被调用关系说明等。 三、命名 1.较短的单词可通过去掉“元音”形成缩写; 2.较长的单词可取单词的头几发符的优先级,并用括号明确表达式的操作顺序, 避免使用默认优先级。 3.使用匈牙利表示法 四、可读性 1.避免使用不易理解的数字,用有意义的标识来替代。 2.不要使用难懂的技巧性很高的语句。

程序详细设计方案

文档编号: 版本号: 密级: XXX详细设计方案(模板) 项目名称:(此处填入项目中文名称) (此处填入项目英文名称) 项目负责人:(此处填入项目负责人) 拟制:年月日审核:年月日批准:年月日

文件控制变更记录 审阅 分发

目录 1引言 5 1.1 编写目的 5 1.2背景 5 1.3 参考资料 5 1.4术语定义及说明 5 22设计概述 5 2.1任务和目标 5 2.1.1需求概述 5 2.1.2运行环境概述 6 2.1.3条件与限制 6 2.1.4详细设计方法和工具 6 3系统详细需求分析 6 3.1详细需求分析 6 3.2接口需求分析 6 4总体方案确认7 4.1系统总体结构确认7 4.2 系统详细界面划分7 4.2.1应用系统与支撑系统的详细界面划分7 4.2.2系统内部详细界面划分7 5系统详细设计7 5.1系统结构设计及子系统划分7 5.2系统功能模块详细设计8 5.3系统界面详细设计8 5.3.1外部界面设计8 5.3.2内部界面设计9 5.3.3用户界面设计9 6数据库系统设计9 6.1设计要求9 6.2信息模型设计9 6.3数据库设计9 6.3.1设计依据9 6.3.2数据库选型9 6.3.3数据库种类及特点9 6.3.4数据库逻辑结构9 6.3.5物理结构设计10 6.3.6数据库安全10 6.3.7数据字典10 7网络通信系统设计10

7.1设计要求10 7.2网络结构确认10 7.3网络布局设计10 7.4网络接口设计11 88信息编码设计11 8.1代码结构设计11 8.2代码编制11 99维护设计11 9.1系统的可靠性和安全性11 9.2系统及用户维护设计11 9.3系统扩充11 9.4错误处理11 9.4.1出错类别11 9.4.2 出错处理11 9.5 系统调整及再次开发问题12 10系统配置12 10.1配置原则12 10.2硬件配置12 10.3软件配置12 1111关键技术12 11.1关键技术的提出12 11.2关键技术的一般说明12 11.3关键技术的实现方案13 12组织机构及人员配置13 13投资预算概算及资金规划13 14实施计划13 14.1限制13 14.2实施内容和进度安排13 14.3实施条件和措施13 14.4系统测试计划13 14.4.1测试策略14 14.4.2测试方案14 14.4.3预期的测试结果14 14.4.4测试进度计划14 14.5验收标准14

java程序的内存分配

JAVA 文件编译执行与虚拟机(JVM)介绍 Java 虚拟机(JVM)是可运行Java代码的假想计算机。只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行。本文首先简要介绍从Java文件的编译到最终执行的过程,随后对JVM规格描述作一说明。 一.Java源文件的编译、下载、解释和执行 Java应用程序的开发周期包括编译、下载、解释和执行几个部分。Java编译程序将Java源程序翻译为JVM可执行代码?字节码。这一编译过程同C/C++的编译有些不同。当C编译器编译生成一个对象的代码时,该代码是为在某一特定硬件平台运行而产生的。因此,在编译过程中,编译程序通过查表将所有对符号的引用转换为特定的内存偏移量,以保证程序运行。Java编译器却不将对变量和方法的引用编译为数值引用,也不确定程序执行过程中的内存布局,而是将这些符号引用信息保留在字节码中,由解释器在运行过程中创立内存布局,然后再通过查表来确定一个方法所在的地址。这样就有效的保证了Java的可移植性和安全性。 运行JVM字节码的工作是由解释器来完成的。解释执行过程分三部进行:代码的装入、代码的校验和代码的执行。装入代码的工作由"类装载器"(class loader)完成。类装载器负责装入运行一个程序需要的所有代码,这也包括程序代码中的类所继承的类和被其调用的类。当类装载器装入一个类时,该类被放在自己的名字空间中。除了通过符号引用自己名字空间以外的类,类之间没有其他办法可以影响其他类。在本台计算机上的所有类都在同一地址空间内,而所有从外部引进的类,都有一个自己独立的名字空间。这使得本地类通过共享相同的名字空间获得较高的运行效率,同时又保证它们与从外部引进的类不会相互影响。当装入了运行程序需要的所有类后,解释器便可确定整个可执行程序的内存布局。解释器为符号引用同特定的地址空间建立对应关系及查询表。通过在这一阶段确定代码的内存布局,Java很好地解决了由超类改变而使子类崩溃的问题,同时也防止了代码对地址的非法访问。 随后,被装入的代码由字节码校验器进行检查。校验器可发现操作数栈溢出,非法数据类型转化等多种错误。通过校验后,代码便开始执行了。 Java字节码的执行有两种方式: 1.即时编译方式:解释器先将字节码编译成机器码,然后再执行该机器码。 2.解释执行方式:解释器通过每次解释并执行一小段代码来完成Java字节码程序的所有操作。 通常采用的是第二种方法。由于JVM规格描述具有足够的灵活性,这使得将字节码翻译为机器代码的工作 具有较高的效率。对于那些对运行速度要求较高的应用程序,解释器可将Java字节码即时编译为机器码,从而很好地保证了Java代码的可移植性和高性能。 二.JVM规格描述 JVM的设计目标是提供一个基于抽象规格描述的计算机模型,为解释程序开发人员提很好的灵活性,同时也确保Java代码可在符合该规范的任何系统上运行。JVM对其实现的某些方面给出了具体的定义,特别是对Java可执行代码,即字节码(Bytecode)的格式给出了明确的规格。这一规格包括操作码和操作数的语法和数值、标识符的数值表示方式、以及Java类文件中的J ava对象、常量缓冲池在JVM的存储映象。这些定义为JVM解释器开发人员提供了所需的信息和开发环境。Java的设计者希望给开发人员以随心所欲使用Java的自由。

C语言的代码内存布局具体解释

一个程序本质上都是由BSS 段、data段、text段三个组成的。这种概念在当前的计算机程序设计中是非常重要的一个基本概念,并且在嵌入式系统的设计中也非常重要,牵涉到嵌入式系统执行时的内存大小分配,存储单元占用空间大小的问题。 ●BSS段:在採用段式内存管理的架构中。BSS段(bss segment)一般是指用 来存放程序中未初始化的全局变量的一块内存区域。 BSS是英文Block Started by Symbol的简称。 BSS段属于静态内存分配。 ●数据段:在採用段式内存管理的架构中,数据段(data segment)一般是指 用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。 ●代码段:在採用段式内存管理的架构中,代码段(text segment)一般是指 用来存放程序执行代码的一块内存区域。这部分区域的大小在程序执行前就已经确定,而且内存区域属于仅仅读。 在代码段中。也有可能包括一些仅仅读的常数变量,比如字符串常量等。 程序编译后生成的目标文件至少含有这三个段。这三个段的大致结构图例如以下所看到的: 当中.text即为代码段,为仅仅读。.bss段包括程序中未初始化的全局变量和static变量。 data段包括三个部分:heap(堆)、stack(栈)和静态数据区。 ●堆(heap):堆是用于存放进程执行中被动态分配的内存段。它的大小并不 固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时。新分配的内存就被动态加入到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

栈(stack):栈又称堆栈,是用户存放程序暂时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包含static声明的变量。static意味着在数据段中存放变量)。 除此以外,在函数被调用时。其參数也会被压入发起调用的进程栈中。而且待到调用结束后。函数的返回值也会被存放回栈中。 因为栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们能够把堆栈看成一个寄存、交换暂时数据的内存区。 当程序在运行时动态分配空间(C中的malloc函数),所分配的空间就属于heap。其概念与数据结构中“堆”的概念不同。 stack段存放函数内部的变量、參数和返回地址,其在函数被调用时自己主动分配。訪问方式就是标准栈中的LIFO方式。 (由于函数的局部变量存放在此,因此其訪问方式应该是栈指针加偏移的方式,否则若通过push、pop操作来訪问相当麻烦) data段中的静态数据区存放的是程序中已初始化的全局变量、静态变量和常量。 在採用段式内存管理的架构中(比方intel的80x86系统),BSS 段(Block Started by Symbol segment)一般是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时BSS 段部分将会清零。BSS 段属于静态内存分配。即程序一開始就将其清零了。 比方,在C语言之类的程序编译完毕之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。 text和data段都在可运行文件里(在嵌入式系统里通常是固化在镜像文件里)。由系统从可运行文件里载入;而BSS段不在可运行文件里,由系统初始化。

C语言的内存分配

在任何程序设计环境及语言中,内存管理都十分重要。在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的。因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题。 第1节主要介绍内存管理基本概念,重点介绍C程序中内存的分配,以及C语言编译后的可执行程序的存储结构和运行结构,同时还介绍了堆空间和栈空间的用途及区别。 第2节主要介绍C语言中内存分配及释放函数、函数的功能,以及如何调用这些函数申请/释放内存空间及其注意事项。 3.1 内存管理基本概念 3.1.1C程序内存分配 1.C程序结构 下面列出C语言可执行程序的基本情况(Linux 2.6环境/GCC4.0)。 可以看出,此可执行程序在存储时(没有调入到内存)分为代码区(text)、数据区(data)和未初始化数据区(bss)3个部分。 (1)代码区(text segment)。存放CPU执行的机器指令(machine instructions)。通常,代码区是可共享的(即另外的执行程序可以调用它),因为对于频繁被执行的程序,只需要在内存中有一份代码即可。代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。另外,代码区还规划了局部变量的相关信息。 (2)全局初始化数据区/静态数据区(initialized data segment/data segment)。该区包含了在程序中明确被初始化的全局变量、静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。例如,一个不在任何函数内的声明(全局数据): 使得变量maxcount根据其初始值被存储到初始化数据区中。

这声明了一个静态数据,如果是在任何函数体外声明,则表示其为一个全局静态变量,如果在函数体内(局部),则表示其为一个局部静态变量。另外,如果在函数名前加上static,则表示此函数只能在当前文件中被调用。 (3)未初始化数据区。亦称BSS区(uninitialized data segment),存入的是全局未初始化变量。BSS这个叫法是根据一个早期的汇编运算符而来,这个汇编运算符标志着一个块的开始。BSS区的数据在程序开始执行之前被内核初始化为0或者空指针(NULL)。例如一个不在任何函数内的声明: 将变量sum存储到未初始化数据区。 图3-1所示为可执行代码存储时结构和运行时结构的对照图。一个正在运行着的C编译程序占用的内存分为代码区、初始化数据区、未初始化数据区、堆区和栈区5个部分。 (1)代码区(text segment)。代码区指令根据程序设计流程依次执行,对于顺序指令,则只会执行一次(每个进程),如果反复,则需要使用跳转指令,如果进行递归,则需要借助栈来实现。 代码区的指令中包括操作码和要操作的对象(或对象地址引用)。如果是立即数(即具体的数值,如5),将直接包含在代码中;如果是局部数据,将在栈区分配空间,然后引用该数据地址;如果是BSS区和数据区,在代码中同样将引用该数据地址。 (2)全局初始化数据区/静态数据区(Data Segment)。只初始化一次。 (3)未初始化数据区(BSS)。在运行时改变其值。

C语言程序的存储区域

一、C语言程序的存储区域 C语言编写的程序经过编绎-链接后,将形成一个统一的文件,它由几个部分组成,在程序运行时又会产生几个其他部分,各个部分代表了不同的存储区域: 代码段(Code or Text):代码段由程序中的机器码组成。在C语言中,程序语句进行编译后,形成机器代码。在执行程序的过程中,CPU的程序计数器指向代码段的每一条代码,并由处理器依次运行。 只读数据段(RO data):只读数据段是程序使用的一些不会被更改的数据,使用这些数方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。 已初始化读写数据段(RW data):已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并具有初值,以供程序运行时读写。 未初始化读写数据段(BSS):未初始化读写据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。 堆(heap):堆内存只在程序运行时出现,一般由程序员分配和释放。在具有操作系统的情况下,如果程序员没释放,操作系统可以在程序结束后回收内存。 栈(stack):栈内存只在程序运行时出现,在函数内部使用的变量,函数的参数以及返回值将使用栈空间,栈空间由编译器自动分配和释放。 C语言目标文件的内存布局如下: Code RO data RW data 代码段,只读数据段,读写数据段,未初始化数据段属于静态区域,而堆和栈属于动态区域。代码段,只读数据段和读写数据段将在连接之后产生,未初始化数据段将在程序初始化的时候开辟,而堆和栈将在程序的运行中分配和释放。 C语言程序分为映像和运行时两种状态,在编译连接后形成的映像中,将只包含代码段,只读数据段和读写数据段,在程序运行之前,将动态生成未初始化数据段,在程序运行时还将动态形成堆区域和栈区域。 一般来说,在静态的映像文件中,各个部分称之为节(Section),而在运行时的各个部分称之为段(Segment),有时统称为段。

软件开发流程规范

软 件 开 发 流 程 规 范 德联软件有限责任公司 编制人:侯秀美审核人: 2015 年 8 月 19 日

目录 目录 .........................................................错误!未定义书签。 一、概述......................................................错误!未定义书签。 二、开发流程规范..............................................错误!未定义书签。 系统软硬件开发环境........................................错误!未定义书签。 系统架构(系统组成)......................................错误!未定义书签。 系统功能模块设计..........................................错误!未定义书签。 系统功能开发流程图........................................错误!未定义书签。 开发修改记录..............................................错误!未定义书签。 三、开发代码规范..............................................错误!未定义书签。 文件结构..................................................错误!未定义书签。 文件信息声明..........................................错误!未定义书签。 程序风格..................................................错误!未定义书签。 空行..................................................错误!未定义书签。

软件编程规范总结

软件编程规范总结 本规范的内容包括:基本原则、布局、注释、命名规则、变量常量与类型、表达式与语句、函数与过程、可靠性、可测性、断言与错误处理等。 一、 二、 三、基本原则 1.保持代码的简明清晰,避免过分的编程技巧。 2.所有的代码尽量遵循ANSI C标准。 3.编程时首先达到正确性,其次考虑效率。 4.避免或少用全局变量。 5.尽量避免使用GOTO语句。 6.尽可能重用、修正老的代码。 7.尽量减少同样的错误出现的次数。 四、文件布局 1.头文件必须要避免重复包含。 2.包含标准库头文件用尖括号< >,包含非标准库头文件用双引号“”。 3.遵循统一的顺序书写类的定义及实现。 类的定义(在定义文件中)按如下顺序书写:

公有属性 公有函数 保护属性 保护函数 私有属性 私有函数 类的实现(在实现文件中)按如下顺序书写: 构造函数 析构函数 公有函数 保护函数 私有函数 4.程序中一行的代码和注释不能超过80列。 5.定义指针类型的变量,*应放在变量前。 6.源程序中关系较为紧密的代码应尽可能相邻。 iLength = 10; iWidth = 5; // 矩形的长与宽关系较密切,放在一起。 StrCaption = “Test”; 7.禁止使用TAB键,必须使用空格进行缩进。缩进为4个空格。 8.程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。{ }之内的代码块使用缩进规则对齐。 9.if、else、else if、for、while、do等语句自占一行,执行语句不得紧跟其后。不论执行语句有多少都要加{ }。 if (varible1 < varible2)

JVM内存区域划分

JVM性能调优-JVM内存区域划分 1.程序计数器(线程私有) 程序计数器(Program Counter Register),也有称作为PC寄存器。保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。也就是说是用来指示执行哪条指令的。 由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相被干扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。 在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。 由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。 异常情况: 不存在 2.Java栈(线程私有)

Java栈也称作虚拟机栈(Java Vitual Machine Stack) Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表、操作数栈、指向当前方法所属的类的运行时常量池的引用、方法返回地址、额外的附加信息。当线程执行一个方法时,就会随之创建一个对应的栈帧,并将建立的栈帧压栈。当方法执行完毕之后,便会将栈帧出栈。因此可知,线程当前执行的方法所对应的栈帧必定位于Java栈的顶部。 局部变量表,用来存储方法中的局部变量(包括在方法中声明的非静态变量以及函数形参)。对于基本数据类型的变量,则直接存储它的值,对于引用类型的变量,则存的是指向对象的引用。局部变量表的大小在编译器就可以确定其大小了,因此在程序执行期间局部变量表的大小是不会改变的。 存储内容:引用对象,returnAddress类型。Long和double类型占用2个局部变量空间,其余的数据类型占据一个。局部变量表空间在编译期间完成分配。

每个程序员都应该了解的内存知识

每个程序员都应该了解的内存知识【第一部分】 [编辑的话: Ulrich Drepper最近问我们,是不是有兴趣发表一篇他写的内存方面的长文。我们不用看太多就已经知道,LWN的读者们会喜欢这篇文章的。内存的使用常常是软件性能的决定性因子,而如何避免内存瓶颈的好文章却不好找。这篇文章应该会有所帮助。 他的原文很长,超过100页。我们把它分成了7篇,每隔一到两周发表一篇。7篇发完后,Ulrich会把全文发出来。对原文重新格式化是个很有挑战性的工作,但愿结果会不错吧。为了便于网上阅读,我们把Ulrich的脚注{放在了文章里},而互相引用的超链接(和[参考书目])要等到全文出来才能提供。 非常感谢Ultich,感谢他让LWN发表这篇文章,期待大家在不久的将来都能写出内存优化很棒的软件。] 1 简介 早期计算机比现在更为简单。系统的各种组件例如CPU,内存,大容量存储器和网口,由于被共同开发因而有非常均衡的表现。例如,内存和网口并不比CPU在提供数据的时候更(特别的)快。 曾今计算机稳定的基本结构悄然改变,硬件开发人员开始致力于优化单个子系统。于是电脑一些组件的性能大大的落后因而成为了瓶颈。由于开销的原因,大容量存储器和内存子系统相对于其他组件来说改善得更为缓慢。 大容量存储的性能问题往往靠软件来改善: 操作系统将常用(且最有可能被用)的数据放在主存中,因为后者的速度要快上几个数量级。或者将缓存加入存储设备中,这样就可以在不修改操作系统的前提下提升性能。{然而,为了在使用缓存时保证数据的完整性,仍然要作出一些修改。}这些内容不在本文的谈论范围之内,就不作赘述了。 而解决内存的瓶颈更为困难,它与大容量存储不同,几乎每种方案都需要对硬件作出修改。目前,这些变更主要有以下这些方式: ?RAM的硬件设计(速度与并发度) ?内存控制器的设计 ?CPU缓存 ?设备的直接内存访问(DMA) 本文主要关心的是CPU缓存和内存控制器的设计。在讨论这些主题的过程中,我们还会研究DMA。不过,我们首先会从当今商用硬件的设计谈起。这有助于我们理解目前在使用内存子系统时可能遇到的问题和限制。我们还会详细介绍RAM的分类,说明为什么会存在这么多不同类型的内存。 本文不会包括所有内容,也不会包括最终性质的内容。我们的讨论范围仅止于商用硬件,而且只限于其中的一小部分。另外,本文中的许多论题,我们只会点到为止,以达到本文目标为标准。对于这些论题,大家可以阅读其它文档,获得更详细的说明。 当本文提到操作系统特定的细节和解决方案时,针对的都是Linux。无论何时都不会包含别的操作系统的任何信息,作者无意讨论其他操作系统的情况。如果读者认为他/她不得不使用别的操作系统,那么必须去要求供应商提供其操作系统类似于本文的文档。 在开始之前最后的一点说明,本文包含大量出现的术语“经常”和别的类似的限定词。这里讨论的技术在现实中存在于很多不同的实现,所以本文只阐述使用得最广泛最主流的版本。在阐述中很少有地方能用到绝对的限定词。 1.1文档结构 这个文档主要视为软件开发者而写的。本文不会涉及太多硬件细节,所以喜欢硬件的读者也许不会觉得有用。但是在我们讨论一些有用的细节之前,我们先要描述足够多的背景。 在这个基础上,本文的第二部分将描述RAM(随机寄存器)。懂得这个部分的内容很好,但是此部分的内容并不是懂得其后内容必须部分。我们会在之后引用不少之前的部分,所以心急的读者可以跳过任何章节来读他们认为有用的部分。

C程序(进程)的内存布局

C程序(进程)的内存布局 #include const int a = 10; //全局常量a int main(void) { const int b = 20; //局部常量b int* pa = (int*)&a; int* pb = (int*)&b; *pa = 30; //可以吗?能成功赋值吗? *pb = 30; //可以吗?能成功赋值吗? return0; } 作为编程新手的我,有时候写程序难免会有种迷糊的感觉,虽然写着代码,但总觉得哪里不自在不通透...像上面的代码,我第一次看到的时候根本没有自信回答出来,我觉着应该有不少编程新人和我一样吧>o< 先从(Linux平台下)虚拟内存管理说起, 写C程序时,我们经常会打印一个指针地址,说这个指针指向某某内存地址.可这些地址是真实物理内存地址吗?不是!这些只是虚拟

内存地址. 当一个C程序调入内存开始执行后,在内存中就会产生一个进程.而在多任务操作系统中每个进程都拥有一片属于自己的内存空间(内存沙盘),这个沙盘就是虚拟地址空间,在32位下是一个4GB的大小的地址块,这些虚拟地址通过页表映射到物理内存. 但系统并不会真的一下分配给每一个进程4GB的物理内存空间的映射= =(不现实啊),这4GB只能是说逻辑地址,它会随着进程的真实需要自动扩展映射到物理内存空间,最大到4GB. 4GB(地址0-0xFFFFFFFF)其中1GB必须保留给系统内核(这是Linux平台下),也就是说进程自身只能拥有3GB的地址(0-0xC0000000),如图 我自己画的= =:

代码区:程序(函数)代码所在,由编译而得到的二进制代码被载入至此.代码区是只读的!有执行权限.代码区一般都从0x08048000地址开始(linux下).值得注意的是,字符串字面值(如"Hello World")就存储在这个区. 数据段和BSS段:合称静态区(全局区),用来存储静态(全局)变量.区别是前者(数据段)存储的是已初始化的静态(全局)变量,可读写. 后者(BSS段)存储的是未初始化的静态(全局)变量,可读写. 堆:自由存储区.不像全局变量和局部变量的生命周期被严格定

linux进程内存布局

linux进程内存布局 linux进程内存布局 在多任务操作系统中的每一个进程都运行在一个属于它自 己的内存沙盘中。这个沙盘就是虚拟地址空间(virtual address space),在32位模式下它总是一个4GB的内存地址块。这些虚拟地址通过页表(page table)映射到物理内存,页表由操作系统维护并被处理器引用。每一个进程拥有一套属于它自己的页表,但是还有一个隐情。只要虚拟地址被使能,那么它就会作用于这台机器上运行的所有软件,包括内核本身。因此一部分虚拟地址必须保留给内核使用:这并不意味着内核使用了那么多的物理内存,仅表示它可支配这么大的地址空间,可根据内核需要,将其映射到物理内存。内核空间在页表中拥有较高的特权级(ring 2或以下),因此只要用户态的程序试图访问这些页,就会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存。内核代码和数据总是可寻址的,随时准备处理中断和系统调用。与此相反,用户模式地址空间的映射随进程切换的发生而不断变化:色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。在上面的例子中,Firefox使用了相当多的虚拟地址

空间,因为它是传说中的吃内存大户。地址空间中的各个条带对应于不同的内存段(memory segment),如:堆、栈之类的。记住,这些段只是简单的内存地址范围,与Intel处理器的段没有关系。不管怎样,下面是一个Linux进程的标准的内存段布局:当计算机开心、安全、可爱、正常的运转时,几乎每一个进程的各个段的起始虚拟地址都与上图完全一致,这也给远程发掘程序安全漏洞打开了方便之门。一个发掘过程往往需要引用绝对内存地址:栈地址,库函数地址等。远程攻击者必须依赖地址空间布局的一致性,摸索着选择这些地址。如果让他们猜个正着,有人就会被整了。因此,地址空间的随机排布方式逐渐流行起来。Linux 通过对栈内存映射段、堆的起始地址加上随机的偏移量来打乱布局。不幸的是,32 位地址空间相当紧凑,给随机化所留下的空当不大,削弱了这种技巧的效果。进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储局部变量和函数参数。调用一个方法或函数会将一个新的栈桢(stack frame)压入栈中。栈桢在函数返回时被清理。也许是因为数据严格的遵从LIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈的内容,只需要一个简单的指针指向栈的顶端即可。因此压栈(pushing)和退栈(popping)过程非常迅速、准确。另外,持续的重用栈空间有助于使活跃的栈内存保持在CPU缓存中,从而加速访问。进程中的每一个线程

C 内存编程

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等。 写这篇文章源于我在论坛上看到的一个贴子。有人问VC使用了哪种方式来实现虚继承。当时我写了一点代码想验证一下,结果发现情况比我想象的要复杂。所以我就干脆认真把相关的问题都过了一遍,并记录成本文。 我对于C++对象模型的知识主要来自于Lippman的书《Inside the C++ Object Model》,中译版为候捷翻的《深度探索C++对象模型》,中英版我都看过,不过我还是推荐中译版,因为中译版的确翻得不错,而且候捷加入了很多的图, 并修正了原版中的一些错误。 我所使用的编译器是VC7.1,文中的代码我都在VC7.1上验证通过。如果在其他的编译器下运行需 要作相应的调整,即使是VC7.0和VC6也是如此。不同编译器产生的汇编代码也不一样,如果你在不同编译器上编译文中的代码生成出的汇编代码和我所列出 的不同,也不足为奇。如果你想在其他的编译器上验证这些代码请自行做相应的改动。 另外我发现VC7.1在实现虚继承时所用的方法和Lippman在书中提到的微软所用的方法不同,不过那时还没有VC7.1。有趣的是,Lippman在写那本书时,是在迪斯尼工作,应该是做和三维影片的渲染软件相关的事。而现在他已经到了微软,相信应该是主导VC7.1编译器的设计工作。 在后文中可以看到列出的很多汇编代码,有些明显效率很低。这可能是因为我没有打开编译器的优化开关。打开优化开关,设置不同的优化选项后,编译器可能产生出高效得多的汇编代码。有兴趣的朋友可以自行试试,并和文中列出的汇编代码做一下比较。 为了便于分析和观察对象的内存布局,我把代码生成时的结构成员对齐选项设置为1字节,默认为8字节。如果你在自己的工程下编译文中的代码,请做同样的设置。因为我写了一些函数打印对象中的布局信息,如果对象选项不是1 字节,运行这些代码会出现指针异常错误。 普通类对象的内存布局 首先我们从普通类对象的内存布局开始。C000为一个空类,定义如下: struct C000 {}; 运行如下代码打印它的大小及对象中的内容。 PRINT_SIZE_DETAIL(C000)

c语言内存结构

本章介绍C语言程序的内存布局结构,包括连接过程中目标程序各个段的组成和运行过程中各个段加载的情况。 在本章的学习中,读者应重点关注以下内容: C语言程序在内存中各个段的组成 C语言程序连接过程中的特性和常见错误 C语言程序的运行方式 13.1 C语言程序的存储区域 由C语言代码(文本文件)形成可执行程序(二进制文件),需要经过编译-汇编-连接三个阶段。编译过程把C语言文本文件生成汇编程序,汇编过程把汇编程序形成二进制机器代码,连接过程则将各个源文件生成的二进制机器代码文件组合成一个文件。 C语言编写的程序经过编译-连接后,将形成一个统一文件,它由几个部分组成。在程序运行时又会产生其他几个部分,各个部分代表了不同的存储区域: 1.代码段(Code或Text) 代码段由程序中执行的机器代码组成。在C语言中,程序语句进行编译后,形成机器代码。在执行程序的过程中,CPU的程序计数器指向代码段的每一条机器代码,并由处理器依次运行。 2.只读数据段(RO data) 只读数据段是程序使用的一些不会被更改的数据,使用这些数据的方式类似查表式的操作,由于这些变量不需要更改,因此只需要放置在只读存储器中即可。 3.已初始化读写数据段(RW data) 已初始化数据是在程序中声明,并且具有初值的变量,这些变量需要占用存储器的空间,在程序执行时它们需要位于可读写的内存区域内,并具有初值,以供程序运行时读写。 4.未初始化数据段(BSS) 未初始化数据是在程序中声明,但是没有初始化的变量,这些变量在程序运行之前不需要占用存储器的空间。 5.堆(heap) 堆内存只在程序运行时出现,一般由程序员分配和释放。在具有操作系统的情况下,如果程序没有释放,操作系统可能在程序(例如一个进程)结束后回收内存。 6.栈(stack) 栈内存只在程序运行时出现,在函数内部使用的变量、函数的参数以及返回值将使用栈空间,栈空间由编译器自动分配和释放。 C语言目标文件的内存布局如图13-1所示。

C++程序内存布局

C++程序内存布局探讨(一) 刘昕重庆大学软件学院 摘要: 本文探讨了C++程序内存布局的基础知识,对堆、栈、全局数据区和代码区的概念进行了简单介绍,并介绍了内存对齐和进程地址空间(虚拟内存)方面的知识。 今天一大早起来,收到外校的同学传给我的一道C++面试题,该公司做Windows平台下的C++开发。面试题有一道考C++程序内存布局,很具有代表性。 已知有这样一段代码: #include #include using std::string; using std::cout; using std::endl; int global_a = 5; //全局对象 static global_b = 6; //全局静态对象 int main()

{ int a = 5; //声明一个变量5 char b = 'a'; int c = 8; static int d = 7; cout<<&a<

相关主题
相关文档
最新文档