博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
深度探索C++对象模型 ( 第五部分 )(转)
阅读量:2497 次
发布时间:2019-05-11

本文共 4082 字,大约阅读时间需要 13 分钟。

深度探索
C++
对象模型(
9
介绍
当编译一个
C++
程序时,计算机的内存被分成了
4
个区域,一个包括程序的代码,一个包括所有的全局变量,一个是堆栈,还有一个是堆(
heap
,
我们称堆是自由的内存区域,我们可以通过
new
delete
把对象放在这个区域。你可以在任何地方分配和释放自由存储区。但是要注意因为分配在堆中的对象没有作用域的限制,因此一旦
new
了它,必须
delete
它,否则程序将崩溃,这便是内存泄漏。(
C#
已经通过内存托管解决了这一令人头疼的问题)。
C++
通过
new
来分配内存,
new
的参数是一个表达式,该表达式返回需要分配的内存字节数,这是我以前掌握的关于
new
的知识。
正文
这一章主要是说
Runtime Semantics
执行期语义学。
这是我们平时写的程序片段:
Matrix identity; //
一个全局对象
Main()
{
Matrix m1=identity;
……
return 0;
}
很常见的一个代码片段,雷神从来没有考虑过
identity
如何被构造,或者如何被销毁。因为它肯定在
Matrix m1=identity
之前就被构造出来了,并且在
main
函数结束前被销毁了。我们不用考虑这些问题,好象
C++
就应该这样。但这本书是研究
C++
底层机制的。既然我们在看这本书,说明我们希望了解
C++
的编译器又做了那些大量的工作,使得我们可以这样使用对象。
C++
程序中所有的全局对象都被放在
data segment
中,如果明确赋值,则对象以该值为初值,否则所配置到内存内容为
0
。也就是说,如果我们有以下定义
Int v1=1024;
Int v2;
v1
v2
都被配置于
data segment
v1
值为
1024
v2
值为
0
。(雷神在
VC6
环境用
MFC
编程时中发现如果
int v2;v2
的值不为
0
,而是
-8
,不知为什么?编译器造成的?)。
如果有一个全局对象,并且这个对象有构造函数和析构函数的话,它需要静态的初始化操作和内存释放工作,
C++
是一种跨平台的编程语言,因此它的编译器需要一种可以移植的静态初始化和内存释放的方法。下面便是它的策略。
1
为每一个需要静态初始化的档案产生一个
_sit()
函数,内带构造函数或内联的扩展。
2
为每一个需要静态的内存释放操作的文件中,产生一个
_std()
函数,内带析构函数或内联的扩展。
3
提供一个
_main()
函数,用来调用所有的
_sti()
函数,还有一个
exit()
函数调用所有的
_std()
函数。
先生说:
Sit
可以理解成
static initialization
的缩写。
Std
可以理解成
static deallocation
的缩写。
那么
main
函数会被编译器变成这样:
Matrix identity; //
一个全局对象
Main()
{
_main();//
对所有的全局对象做
static initialization
动作。
Matrix m1=identity;
……
exit();//
对所有的全局对象做
static deallocation
动作。
}
其中
_main()
会有一个对
identity
对象的静态初始化的
_sti
函数,象下面伪码这样:
// matrix_c
是文件名编码
_identity
表示静态对象,这样能够保证向执行文件提供唯一的识别符号
_sti__matrix_c_identity()
{
identity.Matrix:: Matrix(); //
这就是静态初始化
}
相应的在
exit()
函数也会有一个
_std_matrix_c_identity(),
来进行
static deallocation
动作。
但是被静态初始化的对象有一些缺点,在使用异常时,对象不能被放置在
try
区段内。还有对象的相依顺序引出的复杂度,因此不建议使用需要静态初始化的全局对象。
局部静态对象在
C++
底层机制是如何构造和在内存中销毁的呢?
1
导入一个临时对象用来保护局部静态对象的初始化操作。
2
第一次处理时,临时对象为
false
,于是构造函数被调用,然后临时对象被改为
true.
3
临时对象的
true
或者
false
便成为了判断对象是否被构造的标准。
4
根据判断的结果决定对象的析构函数是否执行。
如果一个类定义了构造函数或者析构函数,则当你定义了一个对象数组时,编译器会通过运行库将你的定义进行加工,例如
:
point knots[10]; //
我们的定义
vec_new(&knots,sizeof(point),10,&point::point,0); //
编译器调用
vec_new()
操作。
下面给出
vec_new
()原型,不同的编译器会有差别。
void * vec_new
void *array, //
数组的起始地址
size_t elem_size, //
每个对象的大小
int elem_count, //
数组元素个数
void(*constructor)(void*),
void(*destructor)(void* ,char)
)
对于明显获得初值的元素,
vec_new
()不再有必要,例如:
point knots[10]={
Point(), //knots[0]
Point(1.0,1.0,0.5), //knots[1]
-1.0 //knots[2]
};
会被编译器转换成:
//C++
伪码
Point::Point(&knots[0]);
Point::Point(&knots[1],1.0,1.0,0.5);
Point::Point(&knots[2],-1.0,0.0,0.0);
vec_new(&knots,sizeof(point),10,&point::point,0); //
剩下的元素,编译器调用
vec_new()
操作。
怎么样,很神奇吧。
当编译一个
C++
程序时,计算机的内存被分成了
4
个区域,一个包括程序的代码,一个包括所有的全局变量,一个是堆栈,还有一个是堆(
heap
,
我们称堆是自由的内存区域,我们可以通过
new
delete
把对象放在这个区域。你可以在任何地方分配和释放自由存储区。但是要注意因为分配在堆中的对象没有作用域的限制,因此一旦
new
了它,必须
delete
它,否则程序将崩溃,这便是内存泄漏。(
C#
已经通过内存托管解决了这一令人头疼的问题)。
C++
通过
new
来分配内存,
new
的参数是一个表达式,该表达式返回需要分配的内存字节数,这是我以前掌握的关于
new
的知识,下面看看通过这本书,使我们能够更进一步的了解到些什么。
Point3d *origin=new Point3d; //
我们
new
了一个
Point3d
对象
编译器开始工作,上面的一行代码被转换成为下面的伪码:
Point3d * origin;
If(origin=_new(sizeof(Point3d)))
{
try{
origin=Point3d::Point3d(origin);
}
catch(…){
_delete(origin);
throw;
}
}
delete origin;
会被转换成
(
雷神将书上的代码改为
exception handling
情况
)
if(origin!=0){
try{
Point3d::~Point3d(origin);
_delete(origin);
catch(…){
_delete(origin); //
不知对否?
throw;
}
}
一般来说对于
new
的操作都直截了当,但语言要求每一次对
new
的调用都必须传回一个唯一的指针,解决这个问题的办法是,传回一个指针指向一个默认为
size=1
的内存区块,实际上是以标准的
C
malloc()
来完成。同样
delete
也是由标准
C
free()
来完成。原来如此。
最后这篇笔记再说说临时对象的问题。
T operator+(const T&,const T&); //
如果我们有一个函数
T a,b,c; //
以及三个对象:
c=a+b;
//
可能会导致临时对象产生。用来放置
a+b
的返回值。然后再由
T
copy constructor
把临时对象当作
c
的初值。也有可能直接由拷贝构造将
a+b
的值放到
c
中,这时便不需要临时对象。另外还有一种可能通过操作符的重载定义,经
named return value
优化也可以获得
c
对象。这三种方法结果一样,区别在于初始化的成本。对临时对象书上有很好的总结:
在某些环境下,有
processor
产生的临时对象是有必要的,也是比较方便的,这样的临时对象由编译器决定。
临时对象的销毁应该是对完整表达式求值过程的最后一个步骤。
因为临时对象是根据执行期语义有条件的产生,因此它的生命规则就显得很复杂。
C++
标准要求凡含有表达式执行结果的临时对象,应该保留到对象的初始化操作完成为止。当然这样也会有例外,当一个临时对象被一个引用绑定时,对象将残留,直到被初始化的引用的生命结束,或者超出临时对象的作用域。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10294527/viewspace-126266/,如需转载,请注明出处,否则将追究法律责任。

转载于:http://blog.itpub.net/10294527/viewspace-126266/

你可能感兴趣的文章
shell 脚本部署项目
查看>>
spring cloud zuul网关上传大文件
查看>>
springboot+mybatis日志显示SQL
查看>>
工作流中文乱码问题解决
查看>>
maven打包本地依赖包
查看>>
spring boot jpa 实现拦截器
查看>>
jenkins + maven+ gitlab 自动化部署
查看>>
Pull Request流程
查看>>
Lambda 表达式
查看>>
函数式数据处理(一)--流
查看>>
java 流使用
查看>>
java 用流收集数据
查看>>
java并行流
查看>>
CompletableFuture 组合式异步编程
查看>>
mysql查询某一个字段是否包含中文字符
查看>>
Java中equals和==的区别
查看>>
JVM内存管理及GC机制
查看>>
Java:按值传递还是按引用传递详细解说
查看>>
Java中Synchronized的用法
查看>>
阻塞队列
查看>>