c++有四种方案存储数据
a. 自动存储持续性;
在函数或代码块声明的变量(包括函数参数)的存储持续性是自动的,他们在程序执行到函数或者代码块时被创建,执行完后自动释放。一般有两种:函数参数和局部变量。
b. 静态存储持续性;
在函数定义外定义的变量或者使用static声明的变量存储持续性是静态的。程序运行的整个过程都存在。有三种:内部链接性的静态局部变量和静态成员变量,外部链接性的全局变量。
c. 线程存储持续性,使用threa_local声明的变量生命周期与线程相同;
d. 动态存储持续性, 使用new/delete运算符分配/释放的变量;
作用域和链接
作用域描述了一个名称在文件(翻译单元)中的可见范围。
在 C++ 中,"translation unit" 指的是源代码文件在编译过程中被编译成对象文件的基本单元。它包括一个源代码文件及其所有包含的头文件和其他依赖的文件。简而言之,翻译单元是编译器编译的一个整体部分。
链接性描述了名称如何在不同翻译单元中共享。
外部链接性可以在文件中共享,内部链接性可以在同一个文件中的函数之间共享,自动变量没有链接性,不能共享。
变量的作用域有局部作用域,全局作用域,静态局部,静态全局。类中声明的成员作用域是整个类,名称空间内声明的变量作用域是整个命名空间。全局作用域是名称空间作用域的特例。
函数的作用域是整个类或者名称空间,但不可能是局部的,因为代码块内不能定义函数。
在c++中,存储方式就是通过存储持续性,作用域和链接性三个维度来描述的。
自动存储持续性
函数内声明的函数参数或变量,存储持续性是自动的,作用域是局部的,没有链接性。
代码块内声明的变量亦是如此。
假如有多个同名的局部变量,在当前代码块内声明的变量会覆盖外部的局部变量。
auto关键字在c++11之前表示声明为自动存储变量,这个功能几乎不用。在c++11之后表示为自动推断类型。
c++编译器是如何实现自动变量的?
常用方法是留出一段内存,当成栈来管理自动变量。编译器会提供改变栈长度的选项,为每个函数调用维护一个栈帧,栈帧之间在内存里是相邻的,函数调用时,自动变量分配在栈上,函数退出时自动释放。
当一个函数被调用时,编译器会将返回地址、参数和保存的寄存器等信息推入栈中,并设置新的栈指针以分配栈帧。
函数执行完成后,编译器会恢复保存的寄存器状态,将栈指针恢复到函数调用之前的位置,弹出栈帧,然后跳转到返回地址。
register
关键字用于提示编译器将变量存储在寄存器中,而非内存中。这可以提高访问速度,因为寄存器的读写速度通常比内存快。需要注意的是,现代编译器通常会自动优化寄存器使用,register
关键字的作用在许多情况下并不明显。此外,register
变量不能取地址。
静态持续变量
静态持续性,外部链接性
这种变量一般定义在函数外部,如果要多个文件中共享,需要用extern声明再使用。
如果有同名局部变量又需要用到该全局变量时,用全局作用域解析符::即可。
静态持续性,内部链接性
在函数外部定义的变量使用static修饰时链接性是内部的,只能在该文件中使用。
静态存储持续性,无链接性
说明符和限定符
cv限定符:const和volatile。
const:内存初始化后程序不能再进行修改。除此之外,使用const限定的全局变量,其链接性变成了内部的,相当于使用了static。
volatile:表示即使程序代码没有对内存单元进行修改,其值也可能发生变化。
volatile
的作用
-
防止优化:
- 编译器通常会进行各种优化,比如缓存变量的值到寄存器中,以提高执行效率。如果一个变量被声明为
volatile
,编译器就会在每次访问该变量时都从内存中读取其值,而不是从寄存器中读取。这确保了程序每次都能获取到变量的最新值。
- 编译器通常会进行各种优化,比如缓存变量的值到寄存器中,以提高执行效率。如果一个变量被声明为
-
与硬件寄存器交互:
- 在嵌入式编程中,
volatile
常用于处理硬件寄存器的值,这些寄存器的值可能会由硬件或其他并发线程进行修改。通过声明为volatile
,可以确保程序能够正确读取这些寄存器的最新状态。
- 在嵌入式编程中,
-
多线程编程:
- 在多线程编程中,如果一个变量可能会被多个线程访问并修改,应该将这个变量声明为
volatile
,以确保所有线程都能看到最新的值。然而,volatile
不能保证线程安全或同步,它只是防止编译器优化。
- 在多线程编程中,如果一个变量可能会被多个线程访问并修改,应该将这个变量声明为
mutable
关键字用于允许类的成员变量在 const
成员函数中被修改。它的主要用途是在需要保证对象逻辑不变性但仍需要修改某些成员时提供灵活性。
mutable
的作用
-
允许修改
const
成员函数中的变量:- 通常,在
const
成员函数中,类的成员变量不能被修改。这是因为const
成员函数承诺不修改对象的状态。然而,有时需要在这些函数中修改某些数据(例如缓存)。这时,可以使用mutable
关键字来声明那些允许在const
成员函数中被修改的成员变量。
- 通常,在
-
缓存和延迟计算:
mutable
关键字经常用于实现缓存或延迟计算。通过在const
成员函数中修改缓存变量,可以提高效率而不改变对象的逻辑状态。
函数和链接性
默认情况下,函数的存储持续性是静态的,链接性是外部的。使用static后把链接性改成内部的。
语言链接性
链接程序对每个函数要求有不一样的符号。以函数spiff(22)为例。
在c语言中,编译器将函数名翻译为_spiff。在c++中,由于函数能重载,根据不同参数,函数名可能被翻译为_spiff_i或者_spiff_d。这就是语言链接性。
如果在c++程序中调用已经用c语言编译好的函数,需要用到extern:
extern "C" void spiff(int); // use C protocol for name look-up
extern void spoff(int); // use C++ protocol for name look-up
extern "C++" void spaff(int); // use C++ protocal for name look-up
动态分配
#include <new>
struct chaff
{
char dross[20];
int slag;
};
char buffer1[50];
char buffer2[500];
int main()
{
chaff *p1, *p2;
int *p3, *p4;
p1 = new chaff; // place structure in heap
p3 = new int[20]; // place int array in heap
p2 = new (buffer1) chaff; // place structure in buffer1
p4 = new (buffer2) int[20]; // place structure in buffer2
...
}
常规new运算符分配的内存要用delete来释放。
定位new运算符分配的则不用。