CPP内存模型与名称空间

主要内容

  • 单独编译
  • 存储持续性、作用域、链接性
  • 定位(placement)new运算符
  • 名称空间

单独编译

程序文件结构

  • 头文件:包含结构声明和使用这些结构的函数的原型
    • 函数原型
    • 使用#define或const定义的符号常量
    • 结构声明
    • 类声明
    • 模板声明
    • 内联函数
  • 源代码文件:包含与结构有关的函数的代码
  • 源代码文件:包含调用与结构相关的函数的代码

❗❗❗注意

  • 代表在标准头文件的目录中查找;包含自定义头文件,使用"myhead.h",代表在当前工作目录和源代码目录查找,如果没找到则到标准位置查找

  • 使用预处理指令避免多次包含同一头文件

    #ifndef COORDIN_H
    #define COORDIN_H
    //place include file contents here
    #endif
    

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

CPP11四种存储方案

  • 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。
  • 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。
  • 线程存储持续性(CPP11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。
  • 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。
存储描述 持续性 作用域 链接性 如何声明
自动 自动 代码块 在代码块中
寄存器 自动 代码块 在代码块中,使用关键字register
静态,无链接性 静态 代码块 在代码块中,使用关键字static
静态,外部链接性 静态 文件 外部 不在任何函数内
静态,内部链接性 静态 文件 内部 不在任何函数内,使用关键字static

❗❗❗注意:静态变量默认初始化为0

说明符和限定符

说明符

  • auto:自动类型推断

  • register:在声明中指示寄存器存储

  • static:用在作用域为整个文件的声明中时,表示内部链接性;被用于局部声明中,表示局部变量的存储持续性为静态的

  • extern:引用声明,即声明引用在其他地方定义的变量

  • thread_local(CPP11新增):变量的持续性与其所属线程的持续性相同

  • mutable:结构(或类)变量为const,其mutable成员也可以被修改

    struct data 
    {
        char name[30];
        mutable int accesses;
    }
    
    const data veep = {"claybourne Clodde", 0, ...}; 
    strcpy(veep.name, "Joye Joux");  //not allowed
    veep.accesses++;                 //allowed
    

限定符

  • const:内存被初始化后,程序便不能再对它进行修改;const全局变量的链接性为内部的,每个文件都有自己的一组常量,而不是所有文件共享一组常量,每个定义都是其所属文件私有的,这就是能够将常量定义放在头文件中的原因
  • volatile:不将值缓存到寄存器中,从内存中获取变量的值

动态分配

  • 使用new运算符初始化

    int *pi = new int (6);                   //*pi set to 6
    double *pd = new double (99.99);         //*pd set to 99.99
    struct where {double x; double y; double z;};
    where *one = new where {2.5, 5.3, 7.2};  //CPP11
    int *ar = new int[4] {2, 4, 6, 7};       //CPP11
    
  • new失败:抛出异常std::bad_alloc

  • new运算符重载

    void *operator new(std::size_t);    //used by new
    void *operator new[](std::size_t);  //used by new[]
    

定位(placement)new运算符

new负责在堆(heap)中找到一个足以能够满足要求的内存块。new运算符还有另一种变体,被称为定位(placement)new运算符,能够指定要使用的位置。

int *p1 = new int;              //invokes new(sizeof(int))
int *p2 = new(buffer) int;      //invokes new(sizeof(int), buffer)
int *p3 = new(buffer) int[40];  //invokes new(40*sizeof(int), buffer)

名称空间

using声明和using编译指令

  • using声明由被限定的名称和它前面的关键字using组成:

    using Jill::fetch;  //a using declaration
    
  • using声明使一个名称可用,而using编译指令使所有的名称都可用

    using namespace Jack;  //make all the names in Jack available
    
  • 可以给名称空间创建别名

    namespace my_very_favorite_things {...};
    namespace mvft = my_very_favorite_things;
    
    namespace MEF = myth::elements::fire;
    using MEF::flame;
    
  • 匿名名称空间:提供了链接性为内部的静态变量的替代品

    static int counts;  //static storage, internal 1inkage
    int other();
    int main()
    {
        ...
    }
    
    int other()
    {
        ...
    }
    //采用名称空间的方法如下
    namespace
    {
        int counts;  //static storage, internal linkage
    }
    int other();
    int main()
    {
        ...
    }
    
    int other()
    {
        ...
    }
    

命名空间使用建议

  • 使用在已命名的名称空间中声明的变量,而不是使用外部全局变量
  • 使用在已命名的名称空间中声明的变量,而不是使用静态全局变量
  • 如果开发了一个函数库或类库,将其放在一个名称空间中。事实上,CPP当前提倡将标准函数库放在名称空间std中,这种做法扩展到了来自C语言中的函数。例如,头文件math.h是与C语言兼容的,没有使用名称空间,但CPP头文件cmath应将各种数学库函数放在名称空间std中
  • 仅将编译指令using作为一种将旧代码转换为使用名称空间的权宜之计
  • 不要在头文件中使用using编译指令。首先,这样做掩盖了要让哪些名称可用;另外,包含头文件的顺序可能影响程序的行为。如果非要使用编译指令using,应将其放在所有预处理器编译指令#include之后
  • 导入名称时,首选使用作用域解析运算符或using声明的方法
  • 对于using声明,首选将其作用域设置为局部而不是全局

参考文献

《CPP Primer Plus》