内存模型和名称空间

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

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

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

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

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

  • 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束位置。这种内存的存储持续性为动态,有时被称为自由存储或堆(heap)。

链接性(linkage)描述了名称如何在不同单元间共享。链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为他们不能共享。

自动存储持续性

在默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性。

当程序开始执行这些变量所属的代码块时,将为其的分配内存;当函数结束时,这些变量都将消失(**注意,执行到代码块时,将为变量分配内存,但其作用域的起点为其声明位置)。

如果内部代码块和外部都定义了一个同名的变量,程序执行内部代码块时,将该同名变量解释为局部代码块变量。

自动变量和栈

了解典型的C++编译器如何实现自动变量有助于更深入了解自动变量。由于自动变量的数目随函数的开始而增减,因此程序必须在运行时对自动变量进行管理。

常用的方法是留出一段内存,并将其视为栈,以管理变量的增减。之所以被称栈,是由于新数据被象征性的放在原有数据的上面(也就是说,在相邻的内存单元中,而不是在同一个内存单元),当程序使用完后,将其从栈中删除。栈的默认长度取决于实现,但编译器通常提供改变栈长度的选项。程序使用两个指针来跟踪栈,一个指针指向栈底--栈的开始位置,另一个指针指向栈顶--下一个可用的内存单元。当函数被调用时,其自动变量被加入到栈中,栈顶指针指向变量后面的下一个可用内存单元。函数结束时,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存。

寄存器变量

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

register int count_fast;

这旨在提高访问变量的速度。

静态持续变量

和C语言一样,C++也为静态存储持续性变量提供了三种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。

这三种链接性都在整个程序执行期间存在,与自动变量相比,他们的寿命更长。

编译器将分配固定的内存块来存储所有的静态变量。这些变量在整个程序执行期间一直存在。另外,如果没有显示地初始化静态变量,编译器将把他设置为0.

在默认情况,静态数组和结构将每个元素或成员的所有位都设置为0.

下面介绍如何创建这三种静态持续变量,然后介绍他们的特点。

  1. 要想创建链接性位外部的静态持续变量,必须在代码块的外面声明他;

  2. 要想创建链接性位内部的静态持续变量,必须在代码块的外面声明它,并使用static限定符;

  3. 要创建没有链接性的静态持续变量,必须在代码块内声明他,并使用static限定符;

所有的静态持续变量都有下述初始化特征:未被初始化的静态变量的所有位都被设置为0.这种变量被称为零初始化的(zero-initialized)。

存储描述持续性作用域链接性如何声明
自动自动代码块在代码块中
寄存器自动代码块在代码块中,使用关键字register
静态,无链接性静态代码块在代码块中,使用关键字static
静态,外部链接性静态文件外部不在任何函数内
静态,内部链接性静态文件内部不在任何函数内,使用关键字static

静态持续性、外部链接性

链接性为外部的变量通常简称为外部变量,他们的存储持续性为静态,作用域为整个文件。因此外部变量也成为全局变量

1. 单定义规则

一方面,在每个使用外部变量的文件中,都必须声明他;另一方面,C++有“单定义规则”,该规则指出,变量只能由一次定义。

C++提供了两个变量声明。

一种是定义声明或简称为“定义”,他给变量分配存储空间;

另一种是引用声明或简称为“声明”,他不给变量分配存储空间,因为它引用已有的变量。

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

double up;
extern int blem;
extern char gr;

如果要在多个文件中使用外部变量,只需在一个文件中包含改变量的定义,但在使用该变量的其他文件中,都必须使用关键字extern声明他。

C++提供了作用域解析符(::)。放在变量名前,该运算符使用变量的全局变量。

静态持续性、内部链接性

在多文件程序中,内部链接性和外部链接性之间的差别很有意义。链接性为内部的变量只能在其所属的文件中使用;但常规外部变量具有外部链接性,即可以在其他文件中hi用。

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

注意:在多文件程序中,可以在一个文件(且只能再一个文件)中定义一个外部变量。使用该变量的其他文件必须使用关键字extern声明它。

静态存储持续性、无链接性

这种变量是这样创建的,将static限定符用于在代码块中定义的变量。在代码块中使用static时,将导致局部变量的存储持续性为静态。这意味着虽然该变量只在该代码块中可用,但他在该代码块不处于活动状态时仍然存在。因此在两次函数调用期间,静态局部变量的值将保持不变。(静态变量适用于再生——可以用他们将瑞士银行的密码账号传递到下一个要去的地方)。另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数,将不会像自动变量那样再次被初始化。

说明符和限定符

有些被成为存储说明符*(storage class specifier)或cv-限定符(cv-qualifier)的C++关键字提供了其他有关存储的信息。下面是存储说明符:

  • auto

  • register

  • static

  • extern

  • thread_loacl

  • mutable

在同一个声明中不能使用多个说明符,但thread_loacl除外,它可与static或extern结合使用。

在C++11之前,可以在声明中使用关键字auto指出变量为自动变量;但在C++11中,auto用于自动类型推断。

关键字register用于在声明中指示寄存器存储,而在C++11中,它只是显示地指出变量是自动的、

关键字thread_loacl指出变量的持续性与其所属线程的持续性相同。thread_loacl变量之于线程,犹如常规静态变量之于整个程序。关键字mutable的含义将根据const来解释,因此先来介绍cv-限定符,然后解释他。

  1. cv-限定符:

  • const;

  • volatile;

最常见的cv-限定符时const,它表明内存被初始化后,程序便不能对他进行修改。

关键字volatile表明,即使程序代码没有对内存单元进行修改,其值也可能发生变化。

例如,假设编译器发现,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器中。这种优化假设变量的值在两次使用之间不会变化。如果不将变量声明为 volatile,则编译器将进行这种优化;将变量声明为 volatile,相当于告诉编译器,不要进行这种优化。

  1. mutable

    可以用它来指出,即使结构(或类)变量为const,其某个成员也可以被修改,例如:

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

    veep的const限定符禁止程序修改veep的成员,但accesses成员的mutable说明符是的accesses不受这种限制。

存储方案和动态分配

动态内存由运算符new和delete控制,而不是由作用域和链接性规则控制。因此,可以在一个函数中分配动态内存,而在另一个函数中将其释放。与自动内存不同,动态内存不是LIFO,其分配和释放顺序要取决于new和delete在何时以何种方式被使用。

通常,编译器使用三块独立的内存:一块用于静态变量,一块用于自动变量,,另外一块用于动态存储。

例如,假设在一个函数中包含下面的语句:

float *p_fees=new float [20];

由new分配的80个字节的内存一直保留在内存中,直到使用delete运算符将其释放。但当包含该声明的语句块执行完毕时,p_fees将消失。如果希望另一个函数能够使用这80个字节的内容,则必须将其地址传递或返回给该函数。另一方面,如果将p_fees的链接性声明为外部的,则文件中位于该声明后面的所有函数都可以使用它。另外,通过在另一个文件中使用下述声明,便可在其中使用该指针。

extern float * p_fees;

使用new运算符初始化

如果腰围内置的标量类型(如int或double)分配存储空间并初始化,可在类型名后面加上初始值,并将其用括号括起来:

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

然而,要初始化常规结构或数组,需要使用大括号的列表初始化

struct where{double x;double y;double z;};
where * one =new where {2.5,5.3,7.2};
int *arr=new int [4] {2,4 6 7};

在C++11中,还可将列表初始化用于单值变量:

int *pi = new int {};
double * pd =new double {99.9};

定位new运算符

通常,new负责在堆(heap)中找到一个足以能够满足要求的内存块。

new运算符还有另外一种变体,被称为定位new运算符,他让您能够指定要使用的位置。

程序员可能使用这种特性来设置其内存管理流程、处理需要通过特定地址进行访问的硬件或在特定位置创建对象。

要使用定位new特性,首先需要包含头文件new,他提供了这种版本的new运算符的原型;然后将new运算符用于提供了所需地址的参数。出需要指定参数外,句法与常规new运算符相同。具体地说,使用定位new运算符时,变量后面可以有方括号,也可以有没有。

#include<new>
char buffer[50];
int *p4;
p4=new (buffer) int[20];

这段代码的意思是从buffer中分配空间给一个包含20个元素的int数组。

buffer分配的空间位于静态内存,如果用delete操作这样的内存,则会出错。

名称空间

在C++中,名称可以是变量、函数、结构、枚举、类以及和结构的成员。当随着项目的增大,名称相互冲突的可能性也将增加。使用多个场上的类库时,可能导致名称冲突。例如,两个库可能都定义了名为List、Tree和Node的类,但定义的方式不兼容。用户可能希望希望使用一个库的List类,而使用另一个库的Tree类。这种冲突被称为名称空间问题。

传统的C++名称空间

术语概念:

  1. 声明区域:声明区域时可以在其中声明的区域。例如,可以在函数外面声明全局变量,对于这种变量,其声明区域为其声明所在的文件。对于函数中声明的变量,其声明区域为其声明所在的代码块。

  2. 潜在作用域:变量的潜在作用域从声明点开始,到其声明区域的结尾。因此潜在作用域比声明区域小,这是由于变量必须定义后才能使用。

  3. 变量对程序而言可见的范围被称为作用域。

新的名称空间特性

C++新增了这样一种功能,即通过定义一种新的声明区域来创建命名的名称空间,这样做的目的之一是提供一个声明名称的区域。一个名称空间中的名称不会与另外一个名称空间发生冲突,同时允许程序的其他部分使用该名称空间中声明的东西。

例如:

namespace Jack
{
double pail;
void fetch();
int pal;
struct Well{...};
}
namespace Jill
{
double bucket(double n){...}
double fetch;
int pal;
struct Hill(...);
}

名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中。因此,在默认情况下,在名称空间中声明的名称的链接性为外部的(除非他引用了常量)。

除了用户定义的名称空间外,还存在另一个名称空间——全局名称空间。它对应文件级声明区域,因此前面所说的全局变量现在被描述位于全局名称空间中。任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。因此,Jack中的fetch可以与Jill中的fetch共存,Jill中的Hill可以与外部Hill共存。名称空间中的声明和定义规则同全局声明和定义规则相同。

名称空间是开放的,即可以把名称加入到已有的名称空间中。例如,下面这条语句将名称goose添加到Jill中已有的名称列表中:

namespace Jill
{
char *goose(const char*);
}

同样,原来的Jack名称空间为fetch()提供了原型。可以在该文件后面(或另一个文件)再次使用Jack名称空间来提供该函数的代码。

namespace Jack
{
void fetch()
{
   ...
}
}

当然,需要由一种方法来访问给定的名称空间中的名称。最简单的方法,通过作用域解析符::,使用名称空间来限定该名称:

Jack::pail = 12.34;
Jill::Hill mole;
Jack::fetch();

using声明和using编译指令

using声明使特定的标识符可用,using编译指令使整个名称空间可用。

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

using Jill::fetch;

using声明将特定的名称添加到它所属的声明区域中。完成该指令后,便可使用名称fetch代替Jill::fetch。

using声明使一个名称可用,而using编译指令使所有的名称都可用。using编译指令由名称空间和它钱买你的关键字using namespace组成,它使名称空间中的所有名称都可用。

jack::pal=3;
jill::pal=10;

变量jack::pal和jill:pal是不同的标识符,表示不同的内存单元。然而,如果使用using声明,情况将发生变化:

using jack::pal;
using jill::pal;
pal=4;

事实上,编译器不允许您同时使用上述两个using声明。

using编译指令和using声明之比较

**假设名称空间和声明区域定义了相同的名称。如果试图使用using声明将名称空间中的名称导入到该声明区域,则这两个名称会发生冲突,从而出错。如果使用using编译指令将该名称空间导入到该声明区域,则局部版本将隐藏名称空间版本。

名称空间的其他特性

可以将名称空间声明进行嵌套:

namespace elements
{
    namespace fire
    {
        int flame;
        ...
      }
    float water;
}

这里,flame指的是element:🔥:flame。

同样,可以使用下面的using编译指令使内部的名称可用:

using namespace elements::fire;

另外,也可以在名称空间中使用using编译指令和using声明,如下所示:

namespace myth
{
    using Jill::fetch;
    usiing namespace elements;
    using std::cout;
    using std::cin;
}

假设要访问Jill::fetch.由于Jill::fetch现在位于名称空间myth(在这里,他被叫做fetch)中,因此可以这样访问它:

std::cin>>myth::fetch;

当然,由于它也位于Jill名称空间中,有哪次仍然可以称作Jill::fetch:

std::cout<<Jill::fetch;

如果没有与之冲突的局部变量,则也可以这样做:

using namespace myth;
cin>>fetch;

现在考虑将using编译指令给用于myth名称空间的情况.using编译指令使可传递的.如果 A op B且B op C,则A op C ,则说明操作op使可传递的.例如,>运算符是可传递的.在这个情况下,下面的语句将导入名称空间myth和elements;

using namespace myth;

这条编译指令与下面良好两条编译指令等价:

using namespace myth;
using namespace elements;

可以给名称空间创建别名.例如:

namespace my_very_favorite_things{...};

则可以使用下面的语句让mvft成为my_very_favorite_things的别名:

namespace mvft=my_very_favorite_things;

可以使用这种技术来简化对嵌套名称空间的使用:

namespace MEF=myth::elements::fire;
using MEF::flame;

未命名的名称空#过省略名称空间的名称来创建未命名的名称空间:

namespace 
{
    int ice;
    int bandycoot;
}

这就像后面跟着using编译指令一样,也就是说,在该名称空间中声明的名称的潜在作用域为:从声明点到到声明区域末尾.从这个方面看,他们与全局变量相似.然而,由于这种名称空间没有名称,因此不能显示地使用using编译指令或using声明来使它在其他位置都可用.具体地说,不能在未命名名称空间所属文件之外的其他文件中,使用该名称空间的名称.这提供了链接性为内部的静态变量的替代品.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值