内存模型

1、单独编译,c++程序可以被拆分成多个块放在多个文件中,例如将结构声明,成员函数声明放在头文件中,再把与结构操作的代码放在一个源文件中,再把需要调用结构和结构函数的代码放在另一个文件中,最后编译时仅需用 #include 包含对应的头文件即可。即使使用结构的函数代码需要修改,重新编译时对于定义结构的代码不需要重新编译,仅需和其编译版本进行链接即可。 需要注意的是,如果结构被重复声明会报错,因此头文件中通常包含以下几种:函数原型、#define 和 用const修饰的符号常量、结构的声明、类的声明、模板的声明、内联函数。为防止头文件的重复包含导致重复声明,头文件通常添加以下代码:

//coordin.h
#ifndef COORDIN_H_

#define COORDIN_H_
...//声明,原型等代码

#endif

可防止在第一次包含后的所有包含。

另外,在包含自己的头文件的时候使用#include“my.h”,双引号的包含,编译器会先在当前的工作文件夹里寻找文件,若没找到,则会在系统的标准文件夹查找,而#include<my.h>则只会在系统 标准的文件夹中寻找

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

C++使用四种方案存储数据:

自动存储持续性:在函数定义、代码块中声明的变量(包括函数参数中声明的变量),他们在程序开始执行其所属函数的时候被建立,执行完函数或代码块后就被释放。

静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量,在程序的整个运行过程中均存在。

线程存储持续性:使用关键字thread_local声明的,其生命周期和所属线程一样长。

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

作用域(scope)描述了名称在文件(翻译单元内多大范围可见)如函数内声明的变量只能在该函数中使用,而不能在其他函数中使用。作用域为局部的变量只能在定义它的代码块中使用,代码块是指由花括号括起的一系列语句,如:函数本身就是一个代码块,for中若要包含多条语句则也是一个代码块;全局作用域(也称文件作用域)的变量在定义其的整个文件中均可使用,自动变量的作用域为局部,而静态变量的作用域取决于其它是如何被定义的。函数原型作用域中使用的名称只在包含参数列表中才能使用,这也就是为什么他们的名字不重要。在类中声明的成员的作用域为整个类,在名称空间中声明的变量的作用域为整个名称空间(全局作用域是名称空间作用域的特例)。

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

2.1自动储存持续性:

默认情况下,在函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性,也就是在代码块中定义了变量,则其声明周期及作用域被限制在该代码块中。

变量某些情况下可能出现可见性的变化,如:

void main()
{
    int i = 0;
    ...
    ...
    {
        int i;
        for(int i = 0;;)
        {

        }
    ]
}

  共有三个同名的变量在不同的时候被声明,每个变量都有自己的作用域与持续性,当作用域发生冲突时,执行中会将变量解释成局部变量,即新的定义会隐藏原先的定义,原先的定义在新的变量作用域中不可见,被隐藏了,但其并没有从内存中删除,因为其生命周期并未结束,当新定义生命周期结束,从内存中删除的时候,原先的变量又会变成可见的。

补充:c++11中的auto

当变量声明时默认类型变为auto后,auto关键字被赋予了新的意义,其被用于自动类型推断,可用替代重复的冗长变量类型。

如:

#include<string>
#include<vector>
int main()
{
    std::vector<std::string> vs;
    for (auto i = vs.begin(); i != vs.end(); i++)
    {
        //..
    }
}

   原先auto的位置本应写成: std::vector<std::string> :: literator这里auto就替代了向量迭代器的声明。

或在函数模板中使用

template <typename _Tx,typename _Ty>
void Multiply(_Tx x, _Ty y)
{
    auto v = x*y;
    std::cout << v;
}

    寄存器变量register,在c++11之前表示建议cpu将该变量用寄存器存储。现在只能显式地指出该变量是自动的,使用得较多。

2.2静态持续变量

静态存储持续性变量拥有三种链接性,这三种链接性的静态变量在程序运行的全程中均存在。

要声明外部链接性的静态变量需要在代码块的外部声明,这种变量可以在其他文件中被使用,在其他文件中要使用关键字extern声明它(注意:c++有“单定义规则”,表示变量只能有一次定义,于是c++给出两种变量声明:定义声明,简称定义,它会给变量分配空间;另一种是引用声明,简称声明,其不为变量分配内存空间,只是引用已声明的变量),在程序中,可以用extern显式地表示一个具有外部链接性的变量并对其初始化,而其他文件中使用该变量的时候必须使用extern声明,且不能对其初始化,否则就会视为一个定义,对其分配空间。单定义原则并不意味着不能出现相同标识符的变量,如果在函数中声明了一个与外部链接性静态变量名字相同的变量,则编译器在函数块内将其认为是一个自动类型的变量,且作用域为函数块内。

要声明内部连接性的静态变量需要在代码块的外部声明,且使用stastic限定符

要声明无链接性的静态变量需要在代码块的内部声明,且使用stastic限定符,注意,即使声明所属的代码块没有被执行,其变量仍然在内存中吗,只不过在代码块中才可见并被使用。即其是在程序开始时就被创建和初始化,之后每次调用均不会被重新创建。

所有未被显式初始化的静态变量都会被设置为0

2.3说明符和限定符

存储说明符:auto(c++11之前);register;stastic;extern;thread_local(c++11新增);mutable

限定符:const;volatile

详解const:const的意义是当内存被初始化后,程序便不能再对其进行修改了。

在c++中,全局const定义与使用了stastic说明符一样:

const int n = 10;// 等效于 static int n = 10;
int main()
{

}

原先外部定义的变量为全局变量,具有外部链接性,使用了const限定之后,其链接性变成内部的,这也就是为什么可以在头文件中定义常量,然后不同的文件只要包含同一个头文件就可以使用同一组常量而不起冲突。但内部链接性也导致每一个文件都有自己的一组常量,而不是共享同一组常量。如有必要,可以使用extern覆盖默认的内部链接性,变为外部链接性。

extern const n = 10;

同样的,只有一个文件中的变量声明可以对其初始化,其他文件在使用该常量的时候也需要使用extern说明,但不能对其初始化,以满足c++的单定义原则。

extern n;//when using const value in other files.

当在函数体内或其他代码块中使用const限定时,其作用域为代码块,因此可以在其他代码块中声明同名常量

volatile:volatile 表示该变量是易变的,如一个指针指向某个硬件的缓冲区,硬件可能会改变该变量,或者是两个程序互相影响(多线程)。原本编译器会对重复调用的变量进行优化,当程序在几条语句中重复使用了某个变量的值,编译器会假定该变量的值不会改变,并让这个值存储进寄存器中以优化执行效率。用volatile限定后,表示该值是会改变的,不要进行优化。

mutable:mutable指出即使结构或类是用const限定的,其某些成员仍然是可以被修改的

struct data
{
    char name[30];
    mutable int n;
};

const data veep = {"hello",0};
veep.name = "qwq";//not allowed
veep.n++;//allowed

2.4函数的链接性

c++不允许在函数中定义函数,因此函数全部都是静态存储持续性,默认情况下函数都具有外部链接性,即可以在文件间共享,可以使用extern用在函数原型的声明前,说明函数是在其他文件中被定义的,但要使用其他文件中的函数,一般需要链接程序在已包含的头文件中搜寻,或者是使用多文件编译将另一个文件作为程序的一部分编译。

另外,还可以使用static设置函数的链接性为内部,使之只能在一个文件中使用,但要在函数原型和函数定义前同时使用static说明

static int private(double);

static int private (double n)
{
    ...
}

这样也就说明可以在不同文件中定义同样名称的内部链接函数,与内部链接变量一样,内部链接函数的定义会覆盖外部链接函数的引用声明。

单定义原则同样适用于非内联函数,函数都是静态的,在定义时被分配内存,多文件程序中只能有一个文件有对函数的定义,其他文件需要调用函数的时候需要包含函数原型(即声明),并未分配内存,所以满足单定义原则。

内联函数不受该规则约束,意味着可以将内联函数的定义直接写入头文件中,这样在包含头文件时就可以调用内联函数了而不会在重复包含时造成多定义,但c++要求同一个函数的所有内联定义相同。

补充:c++查找函数的方式

在调用函数的时候,编译器会先判断函数类型,若为内部,则只在该文件中寻找,若为默认或外部的,编译器及链接程序将在所有的程序文件中查找,若仍未找到,才会到库文件中查找。在这个查找过程中,如果发现有两个定义,就会报错。但对于程序文件中定义的函数和库文件中的函数同名,最后执行的是程序文件中的函数定义,因为编译器和链接程序在程序文件中查找到了函数定义之后就不再到库文件中查找了。(但c++保留了标准库的函数名,无法使用与标准库函数同名的函数)。

2.5语言链接性

程序编译时,链接程序要进行链接操作,要求每个函数都有独一无二的标识符,在c语言中,这很容易实现,因为c中每一个函数名只对应一个函数,为满足内部需要,编译器可能会对函数名进行修饰。c++中,由于函数重载,一个函数名会对应多个函数,这时编译器会根据参数将不同的版本翻译成不同的内部名称再进行链接。故在链接时,c语言和c++的函数内部名称修饰约定是不同的,对于其他语言的函数同样也有不同的约定。所以有的时候使用外部定义的函数的时候需要显式地告知编译器和链接程序使用何种语言链接性,语法为在extern后添加语言名称字符串。如

//file 1
void spiff(int);

void spiff(int a)
{
    ...;
}

//file 2
extern "C" void spiff(int);//c语言链接性
extern void spiff(int);//默认使用c++链接性
extern "C++" void spiff(int);//显式使用c++链接性

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值