c++ 内存模型

来源:互联网 发布:西班牙4g手机网络制式 编辑:程序博客网 时间:2024/05/21 15:06

单独编译

c++的编译时单独的,把编译好的文件在通过链接器的东西链接起来就成了一个可以跑的程序,所以我们一般把源代码写在下面两个文件中(只是名字不同罢了)

头文件:包括结构声明和使用这些结构的函数原型

源代码文件:包含与结构有关的函数代码

上面这两个名词是一个很好用的组织程序策略。我们把下面这些东西放在头文件中

1函数原型

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

3结构声明

4类声明

5模板声明

6内联函数


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

在c++的世界中变量函数等等的一切都是有生命周期的,和贡献范围的,而这些的体现在存储持续性,作用域,链接性这三个名词上

持续性

在c++11 按变量的持续时间可以分为下面4中

1自动存储持续性

c++中定义生命的变量 ,在程序执行其所属的函数和代码块是被创建,执行完函数和代码块是被释放,c++有两种存储持续性为自动的变量

2静态存储持续性

在函数外定义的变量或者使用关键词为static定义的变量存储持续性为静态,他们在整个运行过程中都存在 c++有三种存储持续性为静态的变量

3线程存储持续性

thread_local声明的变量,则其生命周期与所属线程生命周期一样长

4动态存储持续性

new 运算符 创建的变量在再heap中 除非用delete释放

reigister

在以前的c++中creigiter是声明一个寄存器变量,而现在这样的事情一般都是由编译器来决定所以现在register是声明一个持续性为自动的变量,存在的唯一原因是向前兼容

作用域和链接

作用域:就是作用的范围
链接性:师傅哦被外部共享(这个外部有很多意思 暂且定位代码块吧)。自动变量没有链接性因为他们不能共享

c++变量作用域有很多种,局部变量的作用域为代码块中,全局的变量作用域为定义位置到文件结尾之间,自动变量为为局部,定义在代码块中的静态变量作用域为局部,定义在代码块以外的静态变量为全局,在函数原型里的参数的作用域为括号内(这就是为什么原型的参数名可有可无)
c++函数的作用域可以是类或整个名称空间(包括全局),但不能是局部的(因为不能再代码块中定义)

说完怎么多之后我们就对变量就有了大体的认识了 ,现在开始讲变量持续的分类吧

自动存储持续性

这种类型的变量一般都是代码块中的变量  ,因为他们的生存周期和作用域的是随着代码块的的周期而开始和消亡的,典型的就是函数中的参数,他们的持续性是自动的,作用域是局部的,没有链接性(一般自动变量是通过栈stack实现的)

静态存储持续性

这种变量就很不行一样了,他不像自动变量那样随便,他从创建的时候就被专门放在一个存储区域,生存周期就像那 一动不动的王八,很长,一直到程序结束。但是根据作用域可以分为下面几种
1外部链接性(文件之间共享)
在代码块外部声明变量
2内部链接性(文件之内共享)
代码块外面声明 且加上static
3无连接性(不能共享)
在代码块内声明且加上 static
注意  :上面三种的生存周期都是全局的 也就是说和程序一起产生和消亡。

你们可能问我还有两种存储类型没说,1 线程存储我不知道2动态存储就是new来的东西,一般在堆(heap),好了叨逼叨逼了半天我们来看看 这些存储类型变量是怎么初始化的吧

静态变量的初始化

在说初始化时我们要解释两个名词,静态初始化,动态初始化,静态初始化就是在编译之前初始化,动态就是编译之后。现在我们再说静态变量初始化。
对于这种类型的变量的初始化 是比较特殊的因为他们要放在一个特殊的地方存储,直到程序结束,所以编译器一开始会进行统一的零初始化(数值变成0,boolen 变成false,指针 零指针,对象空引用)之后编译器在进行上下问关系在赋上值,有初始值的赋上初始值,有表达式大的就计算表达式在赋值,顺便说一下sizeof也是可以在这个时候计算的,如果编译器无法根据上下文得出详细的初始化信息就只能编译之后,进行动态初始化了

c++11 增加constexpr关键字,增加了创建常量表达式的方法(我也不知道干什么用的)


单定义规则

单定义规则就是 编译器必须准确的找到变量不能出现二义性

一方面,在每个使用外部变量中,都必须声明它,另一方面,变量只能有一次定义,为满足这种需求,c++提供两种变量声明,一种是定义声明 ,简称定义,他给变量分配存储空间;另一种引用声明简称声明 他不给变量存储空间

引用声明 关键字extern

如果要在多个文件中使用外部变量,只需在一个文件中包含变量定义,在使用该变量的其他文件中都必须使用extern声明它,这个主要就是用在链接性为外部的变量不重复定义的一项技术  代码就不写了  以后你们在各种实践中就知道怎么用了


说明符和限定符

说完上面概念性的东西,我们来说代码层面的吧。在c++中称为存储说明符或cv限定符提供了存储的信息。

存储说明符

·auto(c++11中不再是说明符,类型后置和自动转换关键字了)
·reigister(c++11中表示自动变量 可忽略)
·static
·extern
·thread_local
·mutable
其中大部分讲过 ,thread_lacal是线程存储,mutable干什么的?如果你设置了一个了一个结构,但是你不想它被修改,通常你会用const,但是如果这个结构就是其中各别变量不想被修改,有些我还是想修改的怎么办,mutable就是干这个的
struct A{int a;mutable int b;}const A s={1,0};s.b++;


cv-限定符

现在专门说说cv限定符(const和volatile),const就不多说了,那么volatile是什么呢,英文意思易变得。在计算机中一个内存地址存储着一些重要消息,而这个地址很可能会时不时的被硬件改写,亦或是两个程序在互相修改这个地址里的信息,而有些编译器会做一些优化,他发现代码中有两个地方使用了同一个变量的值,他会做一个优化,把这个变量存到寄存器。方便读取也保证了值得不变,而volatile就是取消这种优化的。

基本上说完了,但是不知道有没有人发现一个问题。如果我们在头文件中定义一个常量 const int n=1;那么我们在其他源代码文件中导入这个头文件,之后再编译链接,程序会怎么样呢,答案是通过了也运行良好?为什么呢?有人可能问什么为什么。想上面这样定义的的 n。他应该是文件共享的,也就是说最后的编译结果是有很多n,根据单定义规则则是错误的。为什么呢?原因很简单c++做出了特殊的规定const修饰的变量它的链接性就变成了内部的,这就ok了

函数的链接性

c++中的函数链接性是外部的,没有为什么这是规定(解释了为什么代码块中能不能定义函数了)。实际上你可以在原型时使用关键字extern但是这是可选,同时你也可以在函数前面加上static变成内部链接性

语言链接性

简单的说c++或者c语言在对于名字相同的函数重载在内部实现是用不同的名称来定义的。为了让c++可以根据人的意图来 使用c++还是c语言命名规则的话 就可以用如下代码

extern "C" void f();extern "C++"void f();extern void f();

起始页可以用其他说明符,只是上面是c++标准的说明符


用new来完成动态分配

1 使用new
可以用new来为普通类型的变量分配动态内存如下
int*p=new int();//动态分配
上面的是c98标准的 还可以用c++11的大括号初始化 数组结构等

2new失败
当new失败时 将返回std::bad_alloc
3new:运算符,函数,替换函数
new的实现是通过 调用一下函数实现的
new * operator new (std::size_t)new * operator new[](std::size_t)
上面的函数被称为分配函数,位于全局名称空间中,其中std::size_t是使用的typedef定义的一个合适整形
int *p=new intint *pp=new int[40]
将被转换为
int *p=new(sizeof(int))int*p=new(40*szieof(int))
 同样的 delete 和delete []也是通过调用函数来完成的,但是有趣的是c++可以替换这些函数来创建你自己的new和delete
4 定位new
new运算符是由系统来自动分配内存的但是我们可以自定义分配位置。在使用new运算符之前要使用头文件new。之后需要给予地址参数
int a;int[ aarray30];int *p=new(a)int ;int parray=new(aarray)int;//数组名师第一个元素的地址
注意:
1 定位new运算符会覆盖原来位置上的数据,系统是不负责检测原来位置有没有备用
2定位new运算符如果定位的是自动内存分配区域(栈)的话 delete是不管用的,因为delete 只能释放动态内存区域(堆)的。


名称空间

什么是名称空间, 这个就好像两个相同名字的小孩拥有不同名字的爸爸一样 。比如std::out中std就是名称空间(爸爸)的名字而::是作用域解释符(爸爸和孩子之间的纽带)后面的cout就变量(孩子)的名字,再叫小孩的时候我们就可以叫某某的儿子了。

如何创建

在c++中我们定义一种新的声明空间来创建命名空间

namespace A{int a;double b;struct  C{};}

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

2名称空间是开放的,你可以在以后继续添加东西 。例如你可以在头部的名称空间定义函数原型,再在后面的程序中往名称空间添加定义。

using声明和using编译指令

为了不用再每次使用名称空间的时候都在前面加上名称空间的名字,c++有两种办法。
using std::cout;//接下来代码块中的cout使用的都是std::cout;using namespace std;//接下来代码块中使用的都是std里面的变量(如果有的话);

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

如果名称在函数中声明了 在使用using声明再倒入相同名称,编译器报错。使用using编译指令将会,没事,但是名称空间会被局部变量隐藏
namespace A{ int a; } int a; int main(){ using namespace A; int a; cout<<a <<endl; cout<<::a<<endl; cout<<A:;a<<endl; return;}

所以使用using声明比较安全

名称空间的特殊性质

名称空间中可以使用名称空间

namespace C{using A::a;using namespace B;using std::cout;using std::cin;}

编译指令时可以传递的

using namespace C;
等价于using namespace C;using namespace B;

还有名称空间可以相互赋值的,这样可以简化名称空间嵌套的代码量

namespace D=C::B::a;using D::a;
注意
using A::a;
如果a是一个函数但是你只说明了名字,没有具体的返回类型或函数特征 ,如果这个函数有重载将会导入全部版本

未命名的名称空间

未命名的名称空间,从声明开始到代码块结束的区域使用namespace编译指令





















0 0
原创粉丝点击