C++为在内存中存储数据提供了多种选择:

  • 可以选择数据保留在内存中的时间长度(存储持续性);
  • 程序的哪一部分可以访问数据(作用域和链接);
  • 可以使用new来动态地分配内存;定位new运算符提供了这种技术的变种;
  • C++名称空间是另一种控制访问权的方式;
  • 通常大型程序都由多个源代码文件组成,这些文件可能共享一些数据,这样的程序涉及到程序文件的单独编译。

=========================================

单独编译

C++鼓励程序员将组件函数放在独立的文件中。可以单独编译这些文件,然后将它们链接成可执行的程序。(通常C++编译器既编译程序,又管理链接器)如果只修改了一个文件,则可以只重新编译该文件,然后将它与其他文件的编译版本链接。这使得大程序的管理更便捷。(大程序有多个源代码文件,头文件是预处理的范畴,函数原型是给编译器看的,它告诉编译器该怎么处理函数)

例如:UNIX和Linux系统都具有make程序 ,可以跟踪程序依赖的文件以及这些文件的最后修改时间。运行make时,如果它检测到上次编译后修改了源文件,make程序将记住重新构建程序所需的步骤。很多IDE在Project菜单中都提供了类似的工具。

举个案例:将一个程序放在多个文件中将引出新的问题。多个函数都使用了某种结构声明。谁希望出现更多的问题呢?C和C++开发人员都不希望,因此他们提供了#include 来处理这种情况。与其将结构声明加入到每个文件中。不如统一放到头文件中。然后在每个源文件代码中包含该头文件。这样就可以实现结构声明共享了。另外,也可以将函数原型放到头文件中。因此我们可以将原来的程序分成三个部分:

l  头文件:包含结构声明和使用这些结构的函数的原型;

l  源代码文件:包含于结构有关的函数的代码;

l  源代码文件:包含调用与结构相关的函数的代码;

这是一个非常有用的组织程序的策略。例如:如果编写另一个程序时,也需要使用这些函数,则只需包含头文件,并将函数文件添加到项目列表或make列表中即可。

但是注意不能把函数定义放到头文件中,这会引起麻烦。因为这样做的话,可能有多个文件include这个头文件,这就导致多次定义了同一个函数,除非函数是内联的,否则将出现错误。

通常头文件中常包含的内容:

l  函数原型

l  使用#define或const定义的符号常量

l  结构声明

l  类声明

l  模板声明

l  内联函数

结构声明放在头文件中很常见。因为结构声明不创建变量,而只是在源代码中创建声明的结构变量时,告诉编译器如何创建该结构变量。同样,模板声明也不被编译,而是告诉编译器如何生成与源代码中的函数调用相匹配的函数定义。

被声明的const的数据和内联函数由特殊的链接属性,因此可以放到头文件中。

注: “coordin.h” <coordin.h>,前者表示编译器将首先查找当前的工作目录或源代码目录;后者表示编译器将在存储标准头文件的主机系统的文件系统中查找。

编译器:命令行编译器,

不要使用#include来包含源代码文件,这样将导致多重声明。

头文件管理

在同一个文件中只能将同一个头文件包含一次。为了预防在不知情的情况下将头文件包含多次。可能使用了包含了另外一个头文件的头文件。所以可以使用一种标准的C/C++技术可以避免多次包含同一个头文件。

#ifndef COORDIN_H_

#endif

多个库的连接

连接编译模块时,要确保所有对象文件和库都是由同一个编译器生成的。如果有源代码,通常可以用自己的编译器重新编译源代码来消除链接错误。

C++标准允许每个编译器的设计人员以他认为合适的方式实现名称修饰 。因此不同编译创建的二进制模块可能无法正确地链接。

翻译单元和文件的关系。

=========================================

存储持续性、作用域和链接性

存储类别如何影响信息在文件中的共享。C++使用不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间

自动存储持续性 :在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。即这种变量其产生和销毁都是自动进行的。即存储持续性为自动的变量。C++中有两种

静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在整个程序的运行过程中都存在。C++中有三种该变量。

线程存储持续性:(C++11)多核处理器很常见,这些CPU可同时处理多个执行的任务。这让程序能够将计算机放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其声明周期与所属的线程一样长。

动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。

作用域和链接

作用域(scope),描述了名称在文件(翻译单元)的多大范围内可见。

函数中定义的变量只能在该函数中使用,函数中可见。

在文件中函数定义之前定义的变量,可在所有函数中使用。

链接性:描述了名称如何在不同单元间共享。

内链接:只能在一个文件中的函数共享;

外链接:可在文件间共享;

自动变量的名称没有链接性,因为他们不能共享。

C++变量的作用域局部的变量是只在定义它的代码块中可用。代码块是由花括号括起来的一系列语句。代码块可以嵌套。全局作用域(文件作用域)作用的范围是从定义变量的位置开始都文件尾。自动变量是局部作用域。静态变量的作用域是全局还是局部,取决于它是如何被定义的。

C++函数的作用域:不能在函数中定义函数。所以函数的作用域是整个类或整个名称空间。如果函数的作用域是局部的,则函数只对自己可见,这样的函数就无法调用。函数生而为调用。

存储方式是通过:持续性(时间维度)、作用域和链接性(空间维度)来描述的。

自动存储持续性

作用域是局部的,没有链接性。因为自动变量是定义在函数或代码块中。生命期(持续性)是自动的。

自动变量和栈的关系

深入理解自动变量

了解C++编译器如何实现自动变量。由于自动变量的数目岁函数的开始和结束而增减的。所以程序必须对自动变量进行管理。常用的方法是留出一段内存,将其视为栈。栈是先进后出的。最后一个被加到栈中的变量被首先弹出。栈的结构是逻辑上,象征性的。而不是真的有块连续的内存单元做栈。栈的实现原理应该是靠指针链表。栈的长度通常是默认的,编译器也提供改变栈长度的选项。

寄存器变量 register

静态持续变量

函数中的静态变量------作用域仅为局部

内链接的静态变量------加static修饰,作用域仅限于文件

外链接的静态变量------作用域是多个文件

静态说明了这些变量的生命期是整个程序存在的时期。程序不需要使用特殊的装置来管理这些变量。静态变量的数目在程序运行期间是不变的。

静态变量初始化

默认0初始化,还可以对静态变量进行常量表达式和动态初始化。

默认初始化和常量表达式初始化就是静态初始化,就是在编译前初始化好了。

动态初始化,意味着编译后初始化。

int x;

int y =5;

long z = 13*13;

const double pi =4.0 * atan(1.0);  //动态初始化,必须调用函数atan(),要等到函数被链接且程序执行时。

静态持续性、外部链接性

链接性为外部的变量通常称为外部变量,它们的存储持续性为静态。外部变量也相当于全局变量

单定义规则

声明有两种:引用式声明(extern)、定义式声明;

只能定义一次;

全局变量和局部变量

全局变量的作用域很大,易于访问,但是代价很大,就是程序不可靠。

静态持续性、内部链接性

Static限定符修饰作用域为整个文件的变量时,该变量的链接性将为内部。单文件内部

静态存储持续性、无链接性

就是在函数内部创建变量,加上static修饰符,这样的变量的周期还是静态的,但是作用域是局部的。很奇葩吧。这种变量的目的是如果函数多次调用时,该静态局部变量将保持不变,可以用来记录一些变化的值。不会像自动变量那样被初始化掉。

说明符和限定符

存储说明符

Auto(C++11不再是说明符);

Register;

Static;

Extern;

Thread_local (C++11新增)

Mutable;

规则:同一个声明不能使用多个说明符;

限定符

Const

Volatile:这个关键字表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。该关键字的作用是为了改善编译器的优化能力。编译器会对变量进行优化,它会假定变量在两次使用之间不会变化。如果不用volatile的话,编译器就会进行这样的优化。使用voltile则相当于告诉编译器,不要这样优化,会出问题的。因为这个变量在两次使用之间已经被改了。修改它的是硬件。

关于const有个大坑:

根据单定义规则,就是一个变量只能被定义一次,假设这个变量在一个源文件中被定义了。其他源文件只能使用extern关键字来链接这个变量。

还有一种情况就是有一个变量,会被多个源文件使用怎么办,但是不会去修改这个变量。它是在一个文件中定义,然后在其他源文件中extern。这样很麻烦,也不利于代码的移植,和维护。

所以现在有个办法,就是使用const,在头文件中用const定义这个变量,即const变量。然后其他源文件都include这个头文件。这样会造成对单定义规则的破坏吗?并不会,这是因为const关键字有个隐含属性,就是对全局变量的链接性的影响。就是全局变量具有内链接性。这意味着每个文件都有自己的一组const变量(常量),而不是所有文件共享一组常量。这就很好的解决了单定义与常量共享的矛盾了。编程中经常会遇到很多常量,把其定义到头文件中,就会被多个源文件共享。这些常量都是内链接性的。

假如一个文件有一个const常量如下:

cnst int fingers;

另一个文件想引用该常量,则可以使用extern关键字,来进行引用式声明:

extern const int fingers;

函数和链接性

函数的持续性默认都是静态的,函数生而为调用,所以默认情况下链接性也是外链接的,即可以在文件中共享。

所以可以再函数原型中使用extern关键字来表明引用另一个文件中定义的函数。

当然在一个文件中的函数定义前面加上static就可以表明该函数时内链接性的了。

内联函数不接受单定义规则。所以内联函数可以放到头文件中。

语言链接性

链接程序要求不同的函数由不同的符号名。这在C语言中很容易实现。一个名称对应一个函数。

但是C++有函数重载的概念。同一个名称可能对应不同的函数。所以必须将这些函数翻译成不同的符号名称。因此C++编译器会实行名称矫正或符号修饰。为重载函数生成不同的符号名称。这种方法称为语言的链接性。

存储方案和动态分配

动态内存由:运算符new和delete控制,而不是由作用域和链接性规则控制。

通常:编译器使用三块独立的内存:静态变量、自动变量、动态存储;

动态内存需要被跟踪和访问,手段就是指针变量。

存储方案不适用与动态内存,但是适用于指针变量。

如果指针销毁了,这块动态内存就失去控制了,无法访问。这就是内存泄漏的来源。要避免这种情况。

new和free是要成对使用的。

float * p_fees = new float [20];

1   使用new运算符初始化

1        new失败时

2        new:运算符、函数和替换函数

3        定位new运算符

4        定位new的其他形式


前提:大程序由多个源代码文件组成;

源代码文件编译后成中间文件(目标文件)

链接器把这些目标文件链接在一起。

如果一个源文件修改了,只要重新编译该文件,然后把它与其他中间文件重新链接即可。这样就不需要重新编译其他没有被修改的源文件。这就节省了编译时间。

make程序,帮助编译管理;

跟踪程序依赖的文件

检测编译后源文件的修改

#include 就是一条预处理命令,预处理就是编译前的处理。预处理就是把这条预处理指令进行替换,合成新的源文件。

预处理简单讲就是替换;

 

C++_基础5-内存模型的更多相关文章

  1. java多线程的基础-java内存模型(JMM)

    在并发编程中,需要处理两个关键问题:线程之间如何通信,以及线程之间如何同步.通信是指线程之间如何交换信息,在命令式编程中,线程之间的通信机制有两种:内存共享和消息传递.      同步是指程序中用于控 ...

  2. Java基础:内存模型

    1. 引言 2. Java内存模型 3. 内存间的交互操作 1. 引言 考虑到计算机组成的内容: 原始的计算机是CPU用于计算+硬盘用于存储,由于CPU的高速发展和硬盘的缓慢发展,高速的存储需要持续供 ...

  3. JAVA基础3---JVM内存模型

    Java虚拟机执行Java程序的时候需要使用一定的内存,根据不同的使用场景划分不同的内存区域.有公用的区域随着Java程序的启动而创建:有线程私有的区域依赖线程的启动而创建 JVM内存模型大致可以分为 ...

  4. 【Java并发专题之一】Java内存模型

    一.计算机内存模型 针对计算机机器而言,操作系统.JVM程序等其他所有程序都需要遵循内存模型规范.1.CPU技术发展1.1 CPU缓存的出现CPU的发展快于内存条,CPU的运算速度越来越快,内存条的读 ...

  5. Java内存模型_基础

    线程之间的通信机制有两种: 1.共享内存:线程之间共享程序的公共状态,通过写-读内存中的公共状态进行隐式的通信. 2.消息传递:线程之间没有公共状态,线程之间必须发送消息来显示的进行通信 同步:是指程 ...

  6. java内存模型-基础

    基础 并发编程模型的分类 在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在命令式编程中,线程之间 ...

  7. Jvm基础(2)-Java内存模型

    Jvm基础(2)-Java内存模型 主内存和工作内存 Java内存模型包括主内存和工作内存两个部分:主内存用来存储线程之间的共享变量:而工作内存中存储每个线程的相关变量. 如下图所示: 需要注意的是: ...

  8. Java内存模型基础

    Java内存模型的基础 并发编程模型的两个关键问题 在并发编程种,需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在 ...

  9. 《成神之路-基础篇》JVM——Java内存模型(已完结)

    Java内存模型 本文是<成神之路系列文章>的第一篇,主要是关于JVM的一些介绍. 持续更新中 Java内存模型 JVM内存结构 VS Java内存模型 VS Java对象模型(Holli ...

随机推荐

  1. 【codevs3990】中国余数定理2

    [题目描述]Skytree神犇最近在研究中国博大精深的数学.这时,Sci蒟蒻前来拜访,于是Skytree给Sci蒟蒻出了一道数学题:给定n个质数,以及k模这些质数的余数.问:在闭区间[a,b]中,有多 ...

  2. 408. Valid Word Abbreviation有效的单词缩写

    [抄题]: Given a non-empty string s and an abbreviation abbr, return whether the string matches with th ...

  3. 11-内涵段子-爬虫(python+正则)

    爬取内涵段子,使用正则进行简单处理: #_*_ coding: utf-8 _*_ ''' Created on 2018年7月14日 @author: sss function:爬去内涵段子(静态网 ...

  4. https抓包

  5. 数据库 MySQL 之 数据操作

    数据库 MySQL 之 数据操作 一.MySQL数据类型介绍 MySQL支持多种类型,大致可以分为四类:数值.字符串类型.日期/时间和其他类型. ①二进制类型 bit[(M)] 二进制位(101001 ...

  6. 专题2-通过按键玩中断\2440按键中断编程lesson2

    1.程序优化 修改Makefile 把main.c里面的mmu代码复制到mmu.c并修改如下 main.c的修改 由于在bootloader当中一般不会使用MMU,所以 main.c 加入led.c文 ...

  7. using JSTL

    http://docs.oracle.com/javaee/5/tutorial/doc/bnake.html JSTL(JSP Standard Tag Library)

  8. 进程间传递文件描述符fd

    众所周知,子进程会继承父进程已经打开的文件描述符fd,但是fork之后的是不会被继承的,这个时候是否无能无力了?答应是NO.Linux提供了一个系统调用sendmsg,借助它,可以实现进程间传递文件描 ...

  9. cad转shapefile文件

    private ESRI.ArcGIS.Controls.AxTOCControl axTOCControl1; private ESRI.ArcGIS.Controls.AxLicenseContr ...

  10. 编写高质量代码改善C#程序的157个建议——建议60:重新引发异常时使用Inner Exception

    建议60:重新引发异常时使用Inner Exception 当捕获了某个异常,将其包装或重新引发异常的时候,如果其中包含了Inner Exception,则有助于程序员分析内部信息,方便代码调试. 以 ...