Storage class specifiers

一个名字除了拥有它的作用域,还有两个重要性质:storage duration和linkage。

说明符种类

存储类说明符可分为:

  • auto
  • register
  • static
  • extern
  • thread_local
  • mutable

auto C++11已经改变用法了(类型判断)。
registerC++17已废除,线程暂不讨论(暂时还没了解(′д` )…彡…彡)。
mutable对于存储期和链接属性都是无影响的。其作用本来也不是体现在这里。
因此我们只讨论两个修饰符:

specifier storage duration linkage
static static or thread internal
extern static or thread external

storage duration

把线程的存储期去掉,有三种存储期:

  • automatic
  • static
  • dynamic

automatic storage duration

automatic storage duration. The storage for the object is allocated at the beginning of the enclosing code block and deallocated at the end. All local objects have this storage duration, except those declared static, extern or thread_local.

automatic存储期是针对block内创建的局部对象而言的,当block结束就会自动回收原来分配的资源(这里回收的是stack内存)

static storage duration

static storage duration. The storage for the object is allocated when the program begins and deallocated when the program ends. Only one instance of the object exists. All objects declared at namespace scope (including global namespace) have this storage duration, plus those declared with static or extern.

static存储期是程序开始创建直至程序结束的,整个程序中该对象只有一份实体(所以指定初值后再次指定初值无效)。
定义在namespace(包括global)中的对象都具有static存储期,显式static或是extern声明的对象也是如此。

dynamic storage duration

dynamic storage duration. The storage for the object is allocated and deallocated per request by using dynamic memory allocation functions.

dynamic存储期是通过new和delete这些运算符或一些动态分配的函数(比如malloc和free)进行的,由自己根据需求进行分配和回收(如果设置了引用计数,则可实现自动回收)。

linkage

概念

A name that denotes object, reference, function, type, template, namespace, or value, may have linkage. If a name has linkage, it refers to the same entity as the same name introduced by a declaration in another scope. If a variable, function, or another entity with the same name is declared in several scopes, but does not have sufficient linkage, then several instances of the entity are generated.

linkage就是为了告诉链接器(linker)这个名字在处理翻译单元(translation unit)时引用它是否可见。

no linkage

The name can be referred to only from the scope it is in.

Any of the following names declared at block scope have no linkage:

  • variables that aren’t explicitly declared extern (regardless of the static modifier);
  • local classes and their member functions;
  • other names declared at block scope such as typedefs, enumerations, and enumerators.

Names not specified with external, module, (since C++20) or internal linkage also have no linkage, regardless of which scope they are declared in.

no linkage是针对block的声明的名字的,像变量(non-extern)、局部类(及其成员)和其他在block中的名字都没有链接,被限制了当前作用域。这个没有太多可以展开了,接下来两个才是重点。

internal linkage

The name can be referred to from all scopes in the current translation unit.

Any of the following names declared at namespace scope have internal linkage:

  • variables, variable templates (since C++14), functions, or function templates declared static;
  • non-volatile non-template (since C++14) non-inline(since C++17) non-exported (since C++20) const-qualified variables (including constexpr) that aren’t declared extern and aren’t previously declared to have external linkage;
  • data members of anonymous unions.

internal linkage针对namespace中声明的名字,像声明为static的变量和函数(包括模板)、未被声明为extern的const变量(包括constexpr)(=>const默认为internal)等。internal linkage使这些名字被限制在了该名字所在的翻译单元,即无法被其他翻译单元所引用(single source file)。

In addition, all names declared in unnamed namespace or a namespace within an unnamed namespace, even ones explicitly declared extern, have internal linkage.

对于无名命名空间,本身自带static声明属性,即internal linkage,因此即使显式声明为extern也是无效的。
e.g

1
2
3
namespace{int variable=0;}
//类似于
static int variable=0;

这里我们举一个例子来更为直观的认识一下internal的作用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
header.h
static int variable=42;

function1.h
void function1();

function2.h
void function2();

function1.cpp
#include "function1.h"
void function(){variable=10;}

function2.cpp
#include "function2.h"
void function(){variable=123;}

main.cpp
#include "header.h"
#include "function1.h"
#include "function2.h"
#include <iostream>
int main()
{
function1();
function2();
std::cout<<variable<<std::endl; //print 42
}

这里有三个翻译单元:

  • function1.cpp
  • function2.cpp
  • main.cpp

主函数中的function1和function2作用的都不是主函数所在翻译单元的variable,而是他们自己翻译单元的variable,internal linkage的情况下,每个翻译单元同名的被视为不同对象,相当于拷贝,因此是需要更多的内存开销的。

In practice, this means that when you declare a symbol to have internal linkage in a header file, each translation unit you include this file in will get its own unique copy of that symbol. I.e. it will be as if you redefined each such symbol in every translation unit. For objects, this means that the compiler will literally allocate an entirely new, unique copy for each translation unit, which can obviously incur high memory costs.

external linkage

The name can be referred to from the scopes in the other translation units.

Except as noted below, any of the following names declared at namespace scope have external linkage:

  • variables and functions not listed above (that is, functions not declared static, namespace-scope non-const variables not declared static, and any variables declared extern);
  • enumerations;
  • names of classes, their member functions, static data members (const or not), nested classes and enumerations, and functions first introduced with friend declarations inside class bodies;
  • names of all templates not listed above (that is, not function templates declared static).

external linkage与internal linkage一样也是针对namespace的,function只要不是static声明,都是extern(默认的),namespace中的const变量是internal,而non-const变量是external(non-static)。当然,显式声明的extern变量具有external linkage。
对于一个类,其成员函数、static数据成员(无论const)、嵌套类(看做member class)、友元声明都默认为extern。
模板同理,模板函数只要没被显式声明为static即可。

Any of the following names first declared at block scope have external linkage:

  • names of variables declared extern;
  • names of functions.

在block中第一次声明的extern变量和函数(函数是默认extern)是具有external linkage的(不太了解这种情况,暂未遇到过)

In practice, this means that you must define such a symbol in a place where it will end up in one and only one translation unit, typically an implementation file (.c/.cpp), such that it has only one visible definition.

实际上,我们一般将extern声明放在头文件中,而定义放在实现文件中,同时注意我们只在其中一个翻译单元定义唯一的一份,如果在多个翻译单元中重复定义,到时候链接器会找到两份定义,这违反了单一定义原则(one-definition-rule)。更进一步的来讲,如果重复声明而不定义,尽管不会有单一声明原则(声明是可以重复的),但是这是链接器找到了声明且连接不上定义,会给出undefined error,声明与定义是绑定的。

1
2
extern int x;
int x;

const global variable默认是internal,non-const global variable默认是extern,但是上述两种声明并不一样,
实际上extern只是单纯的声明而已,而int x声明并定义了,它可以等效为:

1
extern int x{};

当然,如果是要加extern声明的话,是不需要初始化的,这破坏了extern的原意。

总结

稍微留意一下就会发现,storage duration对应三种内存:

storage duration memory
automatic stack
static static
dynamic heap

也就是说每个对象的存储期实际上就是对应其存储的内存类型。
这都很好理解。


linkage scoped specifier
no scope /
internal one TU static
external another TU extern

linkage有三种,第一种记住block就行了。
具有extern linkage的一般都是放在头文件里,以便#include该文件的所有翻译单元都可以引用与其他地方定义相同的名字。
而具有internal linkage的在每个翻译单元会生成一个独立且不同的对象。
(吐槽:对于这玩意还是得增长些见识可能理解更深,现在还是不够)