《C++ Primer Plus》9. 内存模型和命名空间

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

存储持续性:

  • 自动存储持续性:函数定义中声明的变量及参数
  • 静态存储持续性:函数定义外定义的变量、使用 static 关键字定义的变量
  • 线程存储持续性:使用 thread_local 关键字声明的变量
  • 动态存储持续性:使用 new 分配的变量

作用域:局部、文件、类、命名空间

链接性:外部、内部、无

局部、无链接性文件作用域、内部链接性文件作用域、外部链接性
自动存储持续性函数中声明的函数参数及变量
静态存储持续性代码块中被 static 修饰的变量1. 代码块外部被 static 修饰的变量
2. 代码块外部被 const 修饰,但不被 extern 修饰的变量
1. 代码块外部被 extern const 修饰的变量
2. 代码块外部未被任何关键字修饰的变量
3. 代码块外部只被 extern 修饰,并且进行了初始化的变量

以下是一些例子:

// 外部链接性
extern const int x1 = 1;

// 内部链接性
const int x2 = 2;

// 内部链接性
static int x3 = 3;

// 外部链接性
int x4 = 4;

// 引用
extern int x5;

// 外部链接性,与 int x6 = 6; 等价
extern int x6 = 6;

引用声明

单定义规则(One Definition Rule):变量只能有一次定义。

根据 ODR,C++ 提供了两种变量声明:定义声明、引用声明。引用声明需要使用 extern 关键字修饰,并且不能进行初始化。

引用时优先本文件作用域中的变量:如果本文件中有 static 修饰的全局变量,那么会优先引用它。

// main.cpp
#include <iostream>
static int x = 10;

int main() {
    extern int x;
    std::cout << x << std::endl;
    return 0;
}

// test.cpp
int x = 20;

上述代码会输出 10

说明符与限定符

volatile:向编译器表示“即使程序没有更改对应的内存单元,其值也可能发生变化”,应用场景比如两个程序共享内存、硬件串行端口等。如果不使用该关键字,编译器会优化“多次查找”,中间将该变量缓存到寄存器中。

mutable:用来指出“即使结构体或类的变量被 const 修饰,某成员也可以被修改”

struct Data {
    char name[30];
    mutable int accesses;
    ...
};
const Data veep = { "Name1", 0, ... };
strcpy(veep.name, "Name2");            // not allowed
veep.accesses++;                       // allowed

函数和链接性

static 可将函数的链接性设置为内部

static 修饰函数声明和函数定义有不同的含义:

  • static 修饰函数声明时,它告诉编译器去哪里寻找这个函数,如果声明带有 static,则只在该文件中寻找
  • static 修饰函数定义时,它告诉编译器这个函数定义的链接性,带有 static 时为内部链接性,不带时是外部链接性
// main.cpp
#include <iostream>
static int func() {
    return 1;
}

static int func();

int main() {
    int x = func();
    std::cout << x << std::endl;
    return 0;
}

// test.cpp
int func() {
    return 2;
}

上面的代码会正常运行,输出 1

// main.cpp
#include <iostream>
int func() { ... }
static int func();

int main() { ... }

// test.cpp
int func() { ... }

上面代码会报错,因为单定义规则不允许两个签名相同的函数

// main.cpp
#include <iostream>
static int func();

int main() { ... }

// test.cpp
int func() { ... }

上面代码会报错,因为编译器在 main.cpp 中找不到 func 函数的定义

// main.cpp
#include <iostream>
int func();

int main() { ... }

// test.cpp
static int func() { ... }

上面代码会报错,因为 test.cpp 中 func 函数是内部链接性

命名空间

C++ 有默认的命名空间,叫做全局命名空间(Global Namespace),全局变量这些东西默认放在全局命名空间中。

不同的命名空间中的名称相互独立,在链接时不可见

// main.cpp
int func();

int main() {
    func();
    return 0;
}

// test.cpp
namespace X {
    int func() {
        return 1;
    }
}

上面代码会报错,因为 main.cpp 中 func 的声明放在全局命名空间中,test.cpp 中 func 的声明放在 X 命名空间中,在链接时不可见

  • using 声明: using NS::func;,声明 func 这个名称,如果该名称已经声明,则会编译错误
  • using 编译指令:using namespace NS;,将命名空间中的所有名称导入,如果某个名称已经声明,那么局部版本会隐藏命名空间版本

不要在头文件中使用 using 关键字

未命名空间:内部链接性,文件作用域,下面的两段代码等价:

namespace {
    int counts;
}
int main() { ... }
static int counts;
int main() { ... }