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. 03.WSDL分析

    自己做一个程序放到tomcat里面这个就是服务,自己安装一个oracle,oracle启动之后那它本身就是一种服务. WebService就是HTTP,那么它和HTTP有什么不同呢? HTTP GET ...

  2. Android 创建项目出现No resource found that matches the given name Theme.AppCompat.Light

    关于为何出现No resource found that matches the given name ‘Theme.AppCompat.Light’的原因 这边博客已经写的很清楚了 大家可以参考一下 ...

  3. Docker学习之路(三)Docker网络详解

    1. Docker的4种网络模式 我们在使用docker run创建Docker容器时,可以用--net选项指定容器的网络模式,Docker有以下4种网络模式: host模式,使用--net=host ...

  4. 第十课,ROS仿真2

    Rviz 属性 1.下面以turtlebot_stage inRviz为例 首先安装 sudo apt-get install ros-indigo-turtlebot-simulator 运行 ro ...

  5. PC/APP/H5三端测试的相同与不同

    随着手机应用的不断状态,同一款产品的移动端应用市场占相较PC端也越来越大,那么app与PC端针对这些产品的测试有什么相同与不同之处呢?总结如下: 首先谈一谈相同之处: 一,针对同一个系统功能的测试,三 ...

  6. 【2008nmj】BP二元分类

    在人的大脑里有数以万计的神经元,它们之间通过神经突触来连接.用以判断. BP神经网络 MATLAB实现:

  7. MySQL性能调优与架构设计——第5章 备份与恢复

    第5章 备份与恢复 前言 数据库的备份与恢复一直都是 DBA 工作中最为重要的部分之一,也是基本工作之一.任何正式环境的数据库都必须有完整的备份计划和恢复测试,本章内容将主要介绍 MySQL数据库的备 ...

  8. POJ3041 Asteroids(二分图最小点覆盖)

    Description Bessie wants to navigate her spaceship through a dangerous asteroid field in the shape o ...

  9. Ubuntu在用root账户使用xftp连接时提示拒绝连接

    一般来说Linux不允许使用root账户连接,修改配置 vi /etc/ssh/sshd_config #Authentication: LoginGraceTime PermitRootLogin ...

  10. mvc - view传值到js

    http://www.cnblogs.com/akwwl/p/5238975.html