若不是因为你

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

第一章

第二章

2.2

全局变量在应用程序的数据去分配,而局部变量在应用程序的栈上进行分配。因此,相对于定义的顺序,多个全局变量的地址是递增的,而局部变量是递减的。此外,由于每次调用函数时,栈顶可能发生变化,因此,局部变量的地址是变化的,而全局变量是不变的。

全局变量在编译期就被决定,它存在于可执行模块(.EXE或.DLL等)的文件映像内部。因此,在载入文件的同时,全局变量的内存就被分配了。而局部变量是在例程被调用的时候才被分配的。局部变量总是在栈上分配。

 

通常可以认为:变量由两个部分组成,即变量的自身和变量的内存占用。

“变量自身” 遵循变量分配的基本原则,可以直接由函数SizeOf()取出。如果是简单类型,则存放变量值。

如果变量是复杂的类型(如一些构造类型或字符串类型),则“变量自身”仅存放“变量内存占用”的指针。“变量的内存占用”的大小取决于该变量的数据结构,通常有专用的或独立设计的函数来取大小。

如果没有特殊的代码实现,复杂类型的“局部变量”存在于栈,而“变量的内存占用”存在于堆。

 

类型化常量与全局变量在同一内存空间中被分配,且使用相同的分配规则。但是不包括有关“变量的内存占用”的规则

基于Delphi的常量定义规则,编译器将常量作为立即数处理:将值直接编码到指令中。因此它们不可以有内存占用,当然也不可能有具体的内存地址值。字符串常量是例外的,显然字符串不能当作立即数处理,因此编译器总是将字符串常量与代码放在一起,并将它的地址指针直接编码到指令中。

字符串常量和变量使用的是不同的内存空间。

2.3

简单类型的变量直接分配内存。

短字符串至少分配一个字节。这个字节的地址亦即字符串在内存中占用的首地址,用于存放字符串的长度,称为计数位。自其后,开始存放字符串中的各个字符。因此,短字符串可以存放0-255个字符。

堆栈都是以4字节为单位进行分配的。所以String[20],在堆栈中将被分配24个字节。

PChar类型是典型的使用Null结尾字符串规则的数据类型。也可以将任意的内存块视作Null结尾字符串来定义和操作。Null结尾字符串是一个弱类型检测的定义。在WINAPI中大量的地方使用了Null结尾字符串,所以很多地方随便传个指针,也可能导致无法预测的结果。

很多时候,使用PChar来定义一个普通的指针,其目的并不是要将指针处理成字符串,而是为了地址运算的方便,因为PChar是编译器支持的数据类型,编译器在必要的情况下重定义Char类型,就可以同时支持纯WideChar或AnsiChar核心的操作系统。

长字符串变量总是占用4个字节,所以绝大多数的时候,可以把长字符串当成指针来操作。

空值的长字符串的内存占用为0字节,非空值的长字符串的内存占用为:串长+8字节+1字节

实际上Delphi使用一个空指针来表示空值的长字符串。所以空字符串在堆中没有内存占用。

非空值的情况下,除了为字符串分配应用的内存空间之外,Delphi还在负偏移的位置占用了8个字节,分别保存了串长和串引用计数。

直接修改引用计数是不安全的,系统对字符串有效性的管理将会因此紊乱。而直接修改字符串的长度,可能比操作SetStringRef()更为危险。SetStringLen()会导致系统对字符串的内存管理紊乱。这主要有两方面:

1,结束符#0不会自动添加,结果串在作为“Null结尾字符串”处理时,不会有预想的改变。

2,变量占用内存并没有实际发生变化,访问增加长度后的字符串可能会导致内存存取错误。

 

由于有引用计数的关系,所以,Delphi会管理两个相同的字符串,以及由赋值而得的字符串。

如果在编译期,能确知两个字符串相同,则编译器使两个字符串使用相同的内存占用。

赋值操作会使得两个字符串使用相同的内存占用。

修改字符串的操作,会导致“写复制”,并且使相同内存占用的两个字符串,开始使用不同的内存占用。

直接访问字符串中的元素(以S[I]的形式访问一个字符),会导致整个字符串写复制。

由于短字符串、NULL结尾字符串和字符数组没有引用计数和长字符串相同的专用计数位,所有,上面的关系在这些类型的字符串中并不完全存在。

 

2.5

任何变量的类型检测都是在编译期完成的。因此,变量之间赋值,对于编译器来说,会发生如下的一些事件:

如果两个类型是相同的简单类型,则将源的值复制到目标。

如果两个类型是相同类型的记录或数组,则将简单数据类型的域复制,并添加其他域的引用计数。

如果两个类型是兼容的,则调用类型转换内部例程,并把转换的结果作为目标。

 

2.6

Delphi中,例程中的代码可以对例程入口的值参数进行写操作。为了应付这种情况,Delphi须在进入例程之前,为一个值参数创建一个备份。例程中对值参数的操作都实际发生在这个备份上,而不会对原来的值参数造成影响。

理论上讲,这样的备份应该是对值参数的一个完全复制。但是,Delphi实际上只是对简单类型进行这样的处理,例如将整型值参数的值复制到堆栈,或者将集合放到寄存器。对于复杂的数据类型(例如数组,记录),为了节约内存开销和提高系统性能,编译器对数据类型的每一个部分进行考察,并确定哪些采用“值复制”,哪些采用“引用计数”,并将采用“引用计数”的域的信息附加在类型信息中的一张表里。在运行期根据这张表进行的“引用计数”操作,就是“增加引用”的过程。

写复制技术被用在很多的地方,例如DLL的映像,但这是在运行期由操作系统决定的行为。在Delphi中,写复制是在编译期就被决定的,它通过内核例程和编译器的语义分析两个方面来实现。

只有使用引用计数的数据类型会发生写复制。写复制机制内嵌于类型的基本操作例程中,且只有在数据发生修改时,写复制才发生。

在值参数备份中,针对不同的数据类型,生成备份的方法不同:

如果是简单数据类型,则直接复制参数的值。

如果是具有引用计数位的数据类型,则调用相应的函数产生一个引用。

如果是数组或记录,则将元素或域按照上述规则分别处理。

如果确知在代码中不需要改写入口的值参数,那么可以用const声明它。这样做的结果是编译器将向例程直接传入参数的地址,而不再是使用菜蔬值的引用。正是因此,如果使用指针访问该地址,就可以对原值进行直接修改。

 

 

 

 

 

 

 

 

posted on 2013-02-22 15:08  若不是因为你  阅读(743)  评论(0编辑  收藏  举报