C++的学习之旅——内存模型和名称空间(上)

目录

一、单独编译

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

 1、作用域和链接

2、自动存储持续性

 (1)自动变量和栈

(2)寄存器变量

3、静态持续变量

4、静态持续性、外部链接性

(1)单定义规则

(2)全局变量和局部变量

5、静态持续性、内部链接性

6、静态存储连续性、无链接性

7、说明符和限定符

8、函数链接性

9、语言链接性

 10、存储方案和动态分配

(1)使用new运算符初始化

(2)new失败

(3)new:运算符、函数和替换函数

(4)定位new运算符


一、单独编译

C++和C语言一样,也允许将程序进行模块化设计,即将设计为一个.h文件(头文件)和.c文件。头文件常包含如下内容:

①函数原型

②使用#define 或const定义的符号常量

③结构声明

④类声明

⑤模板声明

⑥内联函数

在包含头文件时,使用""而不是<>,如果头文件包含在尖括号中,编译器将在存储标准头文件的主机文件系统中查找;如果头文件包含在双引号中,编译器将首先查找当前的工作目录或者源代码目录,如果没有找到头文件,将在标准位置找。因而使用自己的头文件应该使用""而不是<>。

在同一个文件中只能将同一头文件包含因此。预处理器编译指令#ifndef避免头文件多次包含。如下,当编译器首次遇到该文件时,COORDIN_H_未定义,则执行#ifndef和#endif之间的内容,若在同一个文件1中重复包含这个头文件,编译器再次遇到时,由于COORDIN_H_已定义,则直接跳到#endif。

#ifndef COORDIN_H_
#define COORDIN_H_
statement
#endif

在IDE中,不要将头文件加入到项目列表中,也不要在源代码文件中#include另一个源代码文件 另外,还需要注意多个库链接不同的编译器可能会将同一个函数生成不同的修饰名词,因此创建的二进制模块大多数情况下都不能正确的链接。在链接编译模块时,需确保对象文件和库都是同一个编译器生成的。

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

C++使用三种(C++11中是四种)不同的方案来存储数据,这些方案的区别在于数据保留在内存中的时间。

①自动存储持续性:在函数定义中声明的变量(包括函数形参)的存储连续性为自动。它们在程序开始执行其所属的函数或者代码块是被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。

②静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量。它们在程序运行过程中都存在。C++有三种这样的变量。

③动态存储持续性:用new运算符分配的内存,在使用dekete释放之前一致存在。同时也可以被称为自由存储(free store)或堆(heap)。

④线性存储持续性(C++11):当前,多核处理器很常见,这些 CPU 可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是使用关键字 thread local 声明的,其生命周期与所属的线程一样长。

 1、作用域和链接

作用域:分为局部和全部,局部仅在当前函数中或代码块(使用{}括起来的一段代码)里,全局在单前文件中定义的位置至结尾。
链接性:分为外部和内部,外部可在文件间共享,内部只能由一个文件中的函数共享。

C++变量的作用域有很多种:

①静态变量的作用域是全局还是局部取决于是如何被定义的。
②函数原型作用域中使用的名称只在包含参数列表的括号内使用。
③类中声明的成员的作用域是整个类。
④在名称空间声明的变量作用域是整个名称空间(全局域是名称空间域的特例)。
⑤C++函数的作用域可以是整个类或者整个名称空间,但不能是局部的(即不能在代码块中定义函数)。

C++存储方式是通过存储持续性、作用域和链接性描述。

2、自动存储持续性

在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。即不同函数间的变量只有在定义他们的函数中才可以使用。但如果在一个代码块中包含另外一个代码块,内部代码块定义了一个变量与外部代码块的变量名字相同,在程序执行内部代码块时,新的定义将会隐藏以前的定义,等到内部代码块执行完毕,外部的定义才可以重新可见。

 (1)自动变量和栈

首先了解一下栈,栈 (stack) 是一种串列形式的 数据结构。这种数据结构的特点是 后入先出 (LIFO, Last In First Out),数据只能在串列的一端 (称为:栈顶 top) 进行 推入 (push) 和 弹出 (pop) 操作。自动变量保存在栈中,函数调用时自动变量入栈(开辟内存存储自动变量),函数结束出栈(释放使用的内存)。程序使用两个指针来跟踪栈,栈底指针指向栈的开始位置,栈顶置针指向下一个可用的内存单元。下图简化描述了函数调用时的栈变化。

(2)寄存器变量

关键字register 最初由C语言引入,建议编译器使用CPU寄存器来存储自动变量。如:

register int count_fast; //

这样可提高访问变量的速度。在C++11中该关键字已经失去了它的用途,现在和以前auto的用途一样显式的指出变量是自动的。

3、静态持续变量

与C语言一样,C++为静态存储持续性变量提供了3种链接性:

①外部连接性:可在其他文件种访问

②内部链接性:只能在当前文件中访问

③无链接性:只能在当前函数或代码块中访问

但是这些变量在程序运行的整个过程中存在。在程序运行时,这些变量被存储在固定的内存块中,另外,若没有显示的初始化静态变量,编译器将设置为0。

下表展示了不同自动变量以及静态变量的区别

 可以看到,静态变量的连接性由关键字static以及声明的位置取决

4、静态持续性、外部链接性

 链接性为外部的变量通常简称为外部变量,它们的存储持续性为静态,作用域为整个文件。外部变量是在函数外部定义的,因此对所有函数而言都是外部的。例如,可以在 main()前面或头文件中定义它们。可以在文件中位于外部变量定义后面的任何函数中使用它,因此外部变量也称全局变量(相对于局部的自动变量)。

(1)单定义规则

在每一个使用外部变量的文件中都必须声明它;同时C++有单定义规则,即变量只能有一次定义。因此C++提供了两种变量声明:

①定义声明,简称为定义,它给变量分配存储空间;

②引用声明,简称为声明,不分配存储空间,它引用已有变量。

引用声明使用关键字extern,且不进行初始化;否则声明为定义,导致分配存储空间

double up;
extern int blem;
extern char blem;

 在多个文件中使用外部变量时,只需要在一个文件中定义,其他文件使用extern声明

(2)全局变量和局部变量

全局变量与局部变量各有好处:

局部变量:能够数据隔离

全局变量:所有函数都能访问,无需传递变量参数。但是程序不可靠,无法保持数据完整性。适用于常量数据,例如月份,星期等数据块。

这种不需要修改的常量数据,还可以使用const来防止数据被修改。

5、静态持续性、内部链接性

 将static限定符用于作用域为整个文件的变量时,该变量的链接性将为内部的。只能在所属的文件中使用。

另外,当一个文件定义了一个静态外部变量,其名称与另外一个文件中的常规外部变量相同,则在该文件中,静态变量将隐藏常规外部变量。

6、静态存储连续性、无链接性

使用static修饰在代码块中定义的局部变量,存储连续性是静态的。改变了只能在代码块中使用,但是代码块不活跃状态时变量依然存在。这种变量只会在启动时进行一次初始化,以后再调用函数时,将不会像自动变量那样重新初始化。 

7、说明符和限定符

存储说明符:

①auto(C++11中已不是说明符)

②register,声明寄存器存储,C++11中功能变成和以前的auto一样显示指出变量是自动的。

③static 

④extern

⑤thread_local(C++新增),thread_local与static或extern结合使用。指出变量的持续性与其所属线程的持续性相同。thread_local之于线程犹如静态变量之于程序。

⑥mutable,mutable指出const修饰的结构或类,其中某个成员也是可以被修改的

cv-限定符:

①const :在C++中,默认情况下全局变量的链接性是外部的,但const限定符全局变量的链接性是内部的,就像使用了static说明符一样,不过可以添加extern关键字来覆盖内部链接性。

②volatile:volatile表明 即使程序未对内存单元进行修改,其值也可能发生变化。比如说将指针指向某个硬件位置,可能是某个端口的信息,在这种情况下可能是硬件修改了数据。或者两个程序共享程序,互相影响。volatile作用是为了改善编译器的优化能力。

8、函数链接性

在C++中,所有的函数存储持续性都自动是静态的,在程序执行的整个过程都存在,默认情况下,函数的链接性为外部, 可以在函数原型使用extern指出函数是在另外一个文件定义的。也可以使用static将函数链接性设置为内部。

程序寻找函数时,若该文件中函数原型指出函数是静态的,编译器只会在该文件中查找,否则将在所有的程序文件中查找。若存在两个定义,则报错;若找不到定义,则会去库中寻找。

9、语言链接性

C++具有多态性,所以同一个函数名可对应多个函数。因此C++编译器执行会进行名词矫正或者名词修饰,为重载函数生成不同的符号名称。例如,spiff(int)转换为 _spiff_i ,spiff(double , double) 转换为 _spiff_d_d。这种修饰被称为C++语言链接。

C语言由于一个名称仅对应一个函数,则函数没有链接性,。
若C++程序链接C语言库,函数原型要指出约定。

extern "C" void count(int);
extern void count(int);
extern "C++" void count(int);

 10、存储方案和动态分配

通常编译器使用三块独立的内存:(1)静态变量;(2)自动变量;(3)动态存储
动态内存由运算符new和delete控制,而非作用域和链接性规则控制。new分配内存,delete释放内存。

(1)使用new运算符初始化

与前面文章讲述的初始化方式相同 

int *pi = new int (6);
double *pd = new double (99.99);

(2)new失败

new可能找不到请求的内存量,之前会返回一个空指针,现在会引发异常std::bad_alloc。

(3)new:运算符、函数和替换函数

可以使用new、new[]、delete、delete[]函数(被称为分配函数),进行内存空间的开辟与释放。

void * operator new (std::size_t);
void * operator new[] (std::size_t);
void operator delete (std::size_t);
void operator delete[] (std::size_t);

//右边为左边的转换
int * pi = new int; //int * pi = new(sizeof(int));
int *pa = new int[40]; //int *pa = new(40 * sizeof(int));
delete pi; // delete (pi);

(4)定位new运算符

原本new和delete操作是在堆(heap)中寻找满足需求的内存块。new还有一个变体,被称为定位new运算符,能够指定要使用的位置,可用这个特性来设置内存管理规范、处理需要通过特定地址进行访问的硬件或者在特定的位置创建对象。使用定位new运算符有如下要点:①包含头文件new

②将new运算符用于提供了所需地址的参数

③变量后面可以后方括号也可以没有

#include <new>

struct chaff
 {
    char dross[20];
    int slag;
};

char buffer1[50];
char buffer2[500]; /使用静态数组为定位new运算符提供内存空间

int main() 
{
    chaff *p1, *p2;
    int *p3, *p4;

    p1 = new chaff;
    p3 = new int[20];
   
    p2 = new (buffer1)chaff;  //从buffer1中分配空间给chaff;
    p4 = new (buffer2)int[20]; //从buffer2中分配空间给一个包含20个元素的int数组。

    p5 = new (buffer2)int[20]; //overwrite p4所用内存
    p6 = new (buffer + 20 * sizeof(int))int[20] ;//使用偏移量,紧跟着p5所占内存后面接着分配20个元素的int数组
}

C++的学习笔记持续更新中~

要是文章有帮助的话

就点赞收藏关注一下啦!

感谢大家的观看

欢迎大家提出问题并指正~

猜你喜欢

转载自blog.csdn.net/qq_47134270/article/details/128666029
今日推荐