四字节对齐,获取结构中元素的偏移量

内存对齐详解

C/C++内存对齐


//C++中虚函数会在结构体中保存一个虚函数指针-四字节

一、什么是字节对齐,为什么要对齐?

    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。

    对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。

 

二、请看下面的结构:

 

struct MyStruct

{

double dda1;

char dda;

int type

};

对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:

sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13

但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?

其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。

类型

对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)

Char

偏移量必须为sizeof(char)即1的倍数

int

偏移量必须为sizeof(int)即4的倍数

float

偏移量必须为sizeof(float)即4的倍数

double

偏移量必须为sizeof(double)即8的倍数

Short

偏移量必须为sizeof(short)即2的倍数

各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。

下面用前面的例子来说明VC到底怎么样来存放结构的。

struct MyStruct

{

double dda1;

char dda;

int type

};

为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof (int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+ 3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。

下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:

struct MyStruct

{

char dda;

double dda1;  

int type

};

这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明)

struct MyStruct

{

char dda;      //偏移量为0,满足对齐方式,dda占用1个字节;

double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8

                    //的倍数,需要补足7个字节才能使偏移量变为8(满足对齐

                   //方式),因此VC自动填充7个字节,dda1存放在偏移量为8

                  //的地址上,它占用8个字节。

int type;    //下一个可用的地址的偏移量为16,是sizeof(int)=4的倍

           //数,满足int的对齐方式,所以不需要VC自动填充,type存

           //放在偏移量为16的地址上,它占用4个字节。

};//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构

   //的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof

   //(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为

   //sizeof(double)=8的倍数。

所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。

VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

否则必须为n的倍数。下面举例说明其用法。

#pragma pack(push)//保存对齐状态

#pragma pack(4)//设定为4字节对齐

struct test

{

char m1;

double m4;

int m3;

};

#pragmapack(pop)//恢复对齐状态

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragmapack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)

 

三、再看下面这个例子

 

#pragma pack(8)

struct S1{

char a;

long b;

};

struct S2 {

char c;

struct S1 d;

long long e;

};

#pragma pack()

sizeof(S2)结果为24.

成员对齐有一个重要的条件,即每个成员分别对齐.即每个成员按自己的方式对齐.

也就是说上面虽然指定了按8字节对齐,但并不是所有的成员都是以8字节对齐.其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节.

S1中,成员a是1字节默认按1字节对齐,指定对齐参数为8,这两个值中取1,a按1字节对齐;成员b是4个字节,默认是按4字节对齐,这时就按4字节对齐,所以sizeof(S1)应该为8;

S2 中,c和S1中的a一样,按1字节对齐,而d 是个结构,它是8个字节,它按什么对齐呢?对于结构来说,它的默认对齐方式就是它的所有成员使用的对齐参数中最大的一个,S1的就是4.所以,成员d就是按4字节对齐.成员e是8个字节,它是默认按8字节对齐,和指定的一样,所以它对到8字节的边界上,这时,已经使用了12个字节了,所以又添加了4个字节的空,从第16个字节开始放置成员e.这时,长度为24,已经可以被8(成员e按8字节对齐)整除.这样,一共使用了24个字节.

a b

S1的内存布局:11**,1111,

c S1.a S1.b d

S2的内存布局:1***,11**,1111,****11111111

 

这里有三点很重要:

1.每个成员分别按自己的方式对齐,并能最小化长度。

2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。

3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。

 

Win32平台下的微软 编译器(cl.exe for 80×86)的对齐策略:

1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;

备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。

2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);

备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。

3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。

备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

文章1

一、内存对齐的原因

大部分的参考资料都是如是说的:

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬

件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问

未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

二、对齐规则

每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编

译命令#pragmapack(n),n=1,2,4,8,16 来改变这一系数,其中的n就是你要指定的“对齐系数”。

规则1:

数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset

为0的地方,以后每个数据成员的对齐按照#pragmapack 指定的数值和这个数据成员自身长度中,比较小的那个进行。

规则2:

结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进

行对齐,对齐将按照#pragmapack 指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

规则3:

结合1、2颗推断:当#pragmapack 的n 值等于或超过所有数据成员长度的时候,这个n

值的大小将不产生任何效果。

三、试验

我们通过一系列例子的详细说明来证明这个规则吧!

我试验用的编译器包括GCC3.4.2 和VC6.0的C 编译器,平台为WindowsXP + Sp2。

我们将用典型的struct对齐来说明。首先我们定义一个struct:

#pragmapack(n) /* n = 1, 2, 4, 8, 16 */

structtest_t

{

inta;

charb;

shortc;

chard;

};

#pragmapack(n)

首先我们首先确认在试验平台上的各个类型的size,经验证两个编译器的输出均为:

sizeof(char)= 1

sizeof(short)= 2

sizeof(int)= 4

我们的试验过程如下:通过#pragmapack(n)改变“对齐系数”,然后察看sizeof(structtest_t)的值。

1、1 字节对齐(#pragma pack(1))

输出结果:sizeof(structtest_t) = 8 [两个编译器输出一致]

分析过程:

1) 成员数据对齐

#pragmapack(1)

structtest_t {

inta; /* 长度4< 1 按1 对齐;起始offset=00%1=0;存放位置区间[0,3]*/

charb; /* 长度1= 1 按1 对齐;起始offset=44%1=0;存放位置区间[4]*/

shortc; /* 长度2> 1 按1 对齐;起始offset=55%1=0;存放位置区间[5,6]*/

chard; /* 长度1= 1 按1 对齐;起始offset=77%1=0;存放位置区间[7]*/

};

#pragmapack()

成员总大小=8

2) 整体对齐

整体对齐系数=min((max(int,short,char), 1) = 1

整体大小(size)=$(成员总大小)按$(整体对齐系数)圆整=8 /* 8%1=0 */ [注1]

2、2 字节对齐(#pragma pack(2))

输出结果:sizeof(structtest_t) = 10 [两个编译器输出一致]

分析过程:

1) 成员数据对齐

#pragmapack(2)

structtest_t {

inta; /* 长度4> 2 按2 对齐;起始offset=00%2=0;存放位置区间[0,3]*/

charb; /* 长度1< 2 按1 对齐;起始offset=44%1=0;存放位置区间[4]*/

shortc; /* 长度2= 2 按2 对齐;起始offset=66%2=0;存放位置区间[6,7]*/

chard; /* 长度1< 2 按1 对齐;起始offset=88%1=0;存放位置区间[8]*/

};

#pragmapack()

成员总大小=9

2) 整体对齐

整体对齐系数=min((max(int,short,char), 2) = 2

整体大小(size)=$(成员总大小)按$(整体对齐系数)圆整=10 /* 10%2=0 */

3、4 字节对齐(#pragma pack(4))

输出结果:sizeof(structtest_t) = 12 [两个编译器输出一致]

分析过程:

1) 成员数据对齐

#pragmapack(4)

structtest_t {

inta; /* 长度4= 4 按4 对齐;起始offset=00%4=0;存放位置区间[0,3]*/

charb; /* 长度1< 4 按1 对齐;起始offset=44%1=0;存放位置区间[4]*/

shortc; /* 长度2< 4 按2 对齐;起始offset=66%2=0;存放位置区间[6,7]*/

chard; /* 长度1< 4 按1 对齐;起始offset=88%1=0;存放位置区间[8]*/

};

#pragmapack()

成员总大小=9

2) 整体对齐

整体对齐系数=min((max(int,short,char), 4) = 4

整体大小(size)=$(成员总大小)按$(整体对齐系数)圆整=12 /* 12%4=0 */

4、8 字节对齐(#pragma pack(8))

输出结果:sizeof(structtest_t) = 12 [两个编译器输出一致]

分析过程:

1) 成员数据对齐

#pragmapack(8)

structtest_t {

inta; /* 长度4< 8 按4 对齐;起始offset=00%4=0;存放位置区间[0,3]*/

charb; /* 长度1< 8 按1 对齐;起始offset=44%1=0;存放位置区间[4]*/

shortc; /* 长度2< 8 按2 对齐;起始offset=66%2=0;存放位置区间[6,7]*/

chard; /* 长度1< 8 按1 对齐;起始offset=88%1=0;存放位置区间[8]*/

};

#pragmapack()

成员总大小=9

2) 整体对齐

整体对齐系数=min((max(int,short,char), 8) = 4

整体大小(size)=$(成员总大小)按$(整体对齐系数)圆整=12 /* 12%4=0 */

5、16 字节对齐(#pragma pack(16))

输出结果:sizeof(structtest_t) = 12 [两个编译器输出一致]

分析过程:

1) 成员数据对齐

#pragmapack(16)

structtest_t {

inta; /* 长度4< 16 按4 对齐;起始offset=00%4=0;存放位置区间[0,3]*/

charb; /* 长度1< 16 按1 对齐;起始offset=44%1=0;存放位置区间[4]*/

shortc; /* 长度2< 16 按2 对齐;起始offset=66%2=0;存放位置区间[6,7]*/

chard; /* 长度1< 16 按1 对齐;起始offset=88%1=0;存放位置区间[8]*/

};

#pragmapack()

成员总大小=9

2) 整体对齐

整体对齐系数=min((max(int,short,char), 16) = 4

整体大小(size)=$(成员总大小)按$(整体对齐系数)圆整=12 /* 12%4=0 */

四、结论

8字节和16字节对齐试验证明了“规则”的第3点:“当#pragmapack 的n 值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果”。另外内存对齐是个很复杂的东西,上面所说的在有些时候也可能不正确。呵呵^_^

[注1]

什么是“圆整”?

举例说明:如上面的8字节对齐中的“整体对齐”,整体大小=9按4 圆整=12

圆整的过程:从9开始每次加一,看是否能被4整除,这里9,10,11均不能被4整除,到12时可以,则圆整结束。

程序校验(环境VC++6.0)

intmain()

{

inta;char b;short c;char d;//ox0012ff1cox0012ff18 ox0012ff14 ox0012ff10

printf("ox%08x",&a);

printf("ox%08x",&b);

printf("ox%08x",&c);

printf("ox%08x\n",&d);

}

文章2

此页面可以通过在dev_c++4.9.9.2运行,并通过小量的更改在其他IDE下运行.   

 

摘要:

本文描述了内存对齐的各种概念和内存管理的其他知识点, 应用相应的程序示例             进行解释.  

备注:

本文资料收集于网络并通过作者整理. 此篇不考虑继承和虚函数虚表问题. 此类            问题分析详见下个版本.   

            

what and why

什么是字节对齐,为什么要对齐?   

    现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始, 但实际情况是在访问特定类型变量的时候经常在特定的内存地址       访问, 这就需要各种类型数据按照一定的规则在空间上排列, 而不是顺序的一个接一个     的排放,这就是对齐.  

对齐的作用和原因?  

    各个硬件平台对存储空间的处理上有很大的不同. 一些平台对某些特定类型的数据只能从某些特定地址开始存取. 比如有些架构的CPU在访问一个没有进行对齐的变量的时候    会发生错误, 那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,    但是最常见的是如果不按照适合其平台要求对齐数据存放进行对齐, 会在存取效率上带    来损失. 比如有些平台每次读都是从偶地址开始, 如果一个int型( 假设为32位系统 )如     果存放在偶地址开始的地方, 那么一个读周期就可以读出这32bit, 而如果存放在奇地址     开始的地方, 就需要2个读周期, 并对两次读出的结果的高低字节进行拼凑才能得到该    32bit数据.显然在读取效率上下降很多.  

      

4个重要概念

1.数据类型自身的对齐值:   

    对于char型数据, 其自身对齐值为1; 对于short型为2; 对于int, float, double类型,   

    其自身对齐值为4单位字节.   

2.结构体或者类的自身对齐值:  

    其成员中自身对齐值最大的那个值.   

3.指定对齐值:  

    #pragma pack (value)时的指定对齐值value.   

4.数据成员, 结构体和类的有效对齐值:  

    自身对齐值和指定对齐值中小的那个值.   

      

有效对齐值拓展 

    有效对齐值n是最终用来决定数据存放地址方式的值. 有效对齐n, 就是表示对齐在n上,也就是说该数据的"存放起始地址 % n = 0 ". 而数据结构中的数据变量都是按定义的     先后顺序来排放的. 第一个数据变量的起始地址就是数据结构的起始地址. 结构体的成     员变量要对齐排放, 结构体本身也要根据自身的有效对齐值圆整( 就是结构体成员变量    占用总长度需要是对结构体有效对齐值的整数倍, 结合下面例子理解 ).   

       

#endif  

 

#include<iostream>  

usingstd::cout;  

usingstd::endl;  

 

void newSet();  

voidnewSet_P();  

 

typedefstruct 

{  

    int id;             //4:[0]....[3]  

    double weight;      //8:[7].....[15]                           原则1  

    float height;        //4:[16]..[19],总长要为8的整数倍,补齐[20]...[23]  原则3  

}ZX;  

 

typedefstruct 

{  

 char name[2];          //2:[0],[1]  

 int id;               //4:[4]...[7]                             原则1  

 

 double score;          //8:[8]....[15]      

 short grade;           //2:[16],[17]          

 ZX b;                  //24:[24]......[47]                        原则2  

}ZX_1;   

 

int main()  

{  

  ZX_1 a;  

  cout << sizeof( ZX_1 ) << "" << sizeof( ZX ) << endl;  

  cout << "改变顺序的对比:" << endl;  

    

  newSet();  

  cout << "利用编译指令#pragma pack( value )进行对比:"<< endl;   

  newSet_P();  

    

  system( "pause" );  

  return 0;  

    

  #if 0  

  输出为:sizeof(ZX_1 ) = 48, sizeof(ZX) = 24   

  调整数据成员的结构就改变了结构的sizeof( value );   

  例如:把ZX中的double weight;提到int id;前面的话就得到sizeof(ZX) = 16;  

  #endif  

}  

 

void newSet()  

{  

    typedef struct 

    {   

        int a;   

        char b;   

        short c;   

    }NEW_1;   

      

    typedef struct 

    {   

        char b;   

        int a;   

        short c;   

    }NEW_2;  

      

    typedef struct 

    {   

        int a;   

        char b;   

        short c;   

    }NEW_0;   

      

    struct   

    {   

        short c;   

        char b;   

        int a;    

    }NEW_3;  

      

    cout << sizeof( NEW_1 ) <<endl;  

    cout << sizeof( NEW_2 ) <<endl;  

    cout << sizeof( NEW_3 ) <<endl;  

}  

 

voidnewSet_P()  

{  

    #pragma pack( 1 )  

    typedef struct 

    {   

        int a;   

        char b;   

        short c;   

    }NEW_1;   

      

    typedef struct 

    {   

        char b;   

        int a;   

        short c;   

    }NEW_2;  

      

    typedef struct 

    {   

        int a;   

        char b;   

        short c;   

    }NEW_0;   

      

    struct   

    {   

        short c;   

        char b;   

        int a;    

    }NEW_3;  

      

    cout << sizeof( NEW_1 ) <<endl;  

    cout << sizeof( NEW_2 ) <<endl;  

    cout << sizeof( NEW_3 ) <<endl;  

    #pragma pack()  

}  

 

#if 0   

 

计算sizeof( value )必需遵循的原则

数据成员对齐规则:  

    结构(struct)(或联合(union))的数据成员, 第一个数据成员放在offset0的地方,    后每个数据成员存储的起始位置要从该成员大小的整数倍开始.( 如int在32位机为4字     节, 则要从4的整数倍地址开始存储)结构体作为成员对齐规则:  

    如果一个结构里有某些结构体成员, 则结构体成员要从其内部最大元素大小的整数倍地址开始存储. (struct a里存有structb, b里有char, int , double等元素, 那b应该从8的整数倍开始存储. )  

收尾工作:  

    结构体的总大小即sizeof的结果. 必须是其内部最大成员的整数倍. 不足的要补齐.  

      

在考虑内存对齐时的编程技巧

    在编程的时候要考虑节约空间的话, 那么我们只需要假定结构的首地址是0,  然后各个变量按照上面的原则进行排列即可, 基本的原则就是把结构中的变量按照类型大小从小    到大声明,尽量减少中间的填补空间. 还有一种就是为了以空间换取时间的效率,我们显    示的进行填补空间进行对齐, 比如: 有一种使用空间换时间做法是显式的插入reserved成员  

    struct A  

    {  

        char a;  

        char reserved[3];//使用空间换时间   

        int b;  

    }   

reserved成员对我们的程序没有什么意义, 它只是起到填补空间以达到字节对齐的目的, 当  

然即使不加这个成员通常编译器也会给我们自动填补对齐, 我们自己加上它只是起到显式的提醒作用.   

 

总结

总之, 内存对齐方式就是把数据类型中的数据成员一个一个的排在内存中, 并且按照上面所  

述的原则就行计算sizeof( type )的值.   

 

补充:

1.    对齐方式设置

在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间(自然对齐:结构中对齐要求最严格的作为对齐字节数,对齐要求最严格即占用空间最大的,例如double比int要求严格)。一般地,可以通过下面的方法来改变缺省的对界条件:  
     ·    使用伪指令#pragma    pack    (n),C编译器将按照n个字节对齐。  
            ·   使用伪指令#pragma    pack    (),取消自定义字节对齐方式。  

 另外,还有如下的一种方式:  
            ·    __attribute((aligned    (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。  
            ·    __attribute__    ((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。

2.    计算偏移-offsetof宏

定义

#defineoffsetof(s,m)   (size_t)&(((s *)0)->m)

 

s是一个结构名,它有一个名为m的成员(s和m 是宏offsetof的形参,它实际是返回结构s的成员m的偏移地址.

(s *)0 是骗编译器说有一个指向类(或结构)s的指针,其地址值0

&((s *)0)->m   是要取得类s中成员变量m的地址. 因基址为0,这时m的地址当然就是m在s中的偏移

最后转换size_t 型,即unsignedint。

 

#defineoffsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER);

 

分析:
(TYPE *)0,
0 强制转换为 TYPE 型指针,记 p = (TYPE *)0p是指向TYPE的指针,它的值是0。那么 p->MEMBER 就是 MEMBER这个元素了,而&(p->MEMBER)就是MENBER的地址,而基地址为0,这样就巧妙的转化为了TYPE中的偏移量。再把结果强制转换为size_t型的就OK了,size_t其实也就是int
typedef __kernel_size_t  size_t;
typedef unsigned int __kernel_size_t;   

 

可见,该宏的作用就是求出MEMBERTYPE中的偏移量。

 

附个人理解:

#defineoffsetof(s,m)  ((size_t)(&(((s*)0)->m)))

将地址0转换为s类型指针,再取变量m的地址,将该地址转换为size_t类型即为变量m在结构s中的相对偏移。

例子

有例子如:
struct   AAA
{
    int   i;
    int   j;
};
  
struct   AAA   *pAAA;
pAAA = new   AAA;
这时,pAAA实际上是一个Pointer, 指向某一确定的内存地址, 如0x1234;
而pAAA->i 整体是一个int型变量,其地址是&(pAAA->i), '&'为取址运算符;
那么&(pAAA->i)一定等于0x1234,因 i 是结构体AAA的第一个元素。
而&(pAAA->j)一定是0x1234 +0x4 = 0x1238; 因为sizeof(int) = 4;
  
这个做法的巧妙之处就是:它把“0”作为上例中的pAAA,那么&(pAAA->j)就是j的offset啦,

解析结果是:
(s*)0,将 0 强制转换为Pointerto "s"   
可以记 pS = (s*)0,pS是指向s的指针,它的值是0;
那么pS->m就是m这个元素了,而&(pS->m)就是m的地址,就是offset啦

 


结构体成员的内存分布与对齐

 

 

我们先看一道IBM和微软的笔试题:

IBM笔试题:

struct{

  short   a1;

short   a2;

  short   a3;

  }A;

  struct{

  long   a1;

  short   a2; 

  }B;  

  sizeof( A)=6,  sizeof(B)=8,为什么?  

  注:sizeof(short)=2,sizeof(long)=4

 

微软笔试题:

struct example1

 {

       short a ;

       long b;

 };

 

 struct example2

 {

 char c;

 example1struct1;

 short e;   

 };

 

int main(int argc, char* argv[])

{

       example2e2;

       intd=(unsigned int)&e2.struct1-(unsigned int)&e2.c;

       printf("%d,%d,%d\n",sizeof(example1),sizeof(example2),d);

       return 0;

}

输出结果?

 

要能清除的分析上面的问题就要搞清楚结构体变量的成员在内存里是如何分布的、成员先后顺序是怎样的、成员之间是连续的还是分散的、还是其他的什么形式?其实这些问题既和软件相关又和硬件相关。所谓软件相关主要是指和具体的编程语言的编译器的特性相关,编译器为了优化CPU访问内存的效率,在生成结构体成员的起始地址时遵循着某种特定的规则,这就是所谓的结构体成员“对齐”;所谓硬件相关主要是指CPU的“字节序”问题,也就是大于一个字节类型的数据如int类型、short类型等,在内存中的存放顺序,即单个字节与高低地址的对应关系。字节序分为两类:Big-Endian和Little-Endian,有的文章上称之为“大端”和“小端”,他们是这样定义的:

Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端;Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

Intel、VAX和Unisys处理器的计算机中的数据的字节顺序是Little-Endian,IBM 大型机和大多数Unix平台的计算机中字节顺序是Big –Endian。

关与Big-Endian和Little-Endian问题本文暂不做详细讨论,本文将以小端机(此处为intel x86架构的计算机)、OS:WindowsXp和VC++6.0编译器来详细讨论结构体成员的“对齐”问题。

前面说了,为了优化CPU访问内存的效率,程序语言的编译器在做变量的存储分配时就进行了分配优化处理,优化规则大致原则是这样:

  对于n字节的元素(n=2,4,8,...),它的首地址能被n整除,这种原则称为“对齐”,如WORD(2字节)的值应该能被2整除的位置,DWORD(4字节)应该在能被4整除的位置。 

对于结构体来说,结构体的成员在内存中顺序存放,所占内存地址依次增高,第一个成员处于低地址处,最后一个成员处于最高地址处,但结构体成员的内存分配不一定是连续的,编译器会对其成员变量依据前面介绍的 “对齐”原则进行处理。对待每个成员类似于对待单个n字节的元素一样,依次为每个元素找一个适合的首地址,使得其符合上述的“对齐”原则。通常编译器中可以设置一个对齐参数n,但这个n并不是结构体成员实际的对齐参数,VC++6.0中结构体的每个成员实际对齐参数N通常是这样计算得到的N=min(sizeof(该成员类型),n)(n为VC++6.0中可设置的值)。

成员的内存分配规律是这样的:从结构体的首地址开始向后依次为每个成员寻找第一个满足条件的首地址x,该条件是x %N =0,并且整个结构的长度必须为各个成员所使用的对齐参数中最大的那个值的最小整数倍,不够就补空字节

结构体中所有成员的对齐参数N的最大值称为结构体的对齐参数

VC++6.0中n 默认是8个字节,可以修改这个设定的对齐参数,方法为在菜单“工程”的“设置”中的“C/C++”选项卡的“分类”中 “Code Generation ”的“Struct member alignment” 中设置,1byte、2byte、4byte、8byte、16byte等几种,默认为8byte

也可以程序控制,采用指令:#pragma  pack(xx)控制

如#pragma  pack(1),1字节对齐,#pragma   pack(4),4字节对齐

#pragma   pack(16),16字节对齐

 

接下来我们将分不同的情况来详细讨论结构体成员的分布情况,顺便提醒一下,常见类型的长度:Int4byte,Short 2byte,Char 1byte,Double 8byte,Long 4byte

 

让我们先看下例:

struct A

{

       char     c;    //1byte

       double   d;    //8byte

       short      s;     //2byte

       int          i;     //4byte

};

int main(int argc, char* argv[])

{

 

       A strua;

       printf("%len:d\n",sizeof(A));

       printf("%d,%d,%d,%d",&strua.c,&strua.d,&strua.s,&strua.i);

       return 0;

}

1)n设置为8byte时

结果:len:24,

1245032,1245040,1245048,1245052

内存中成员分布如下:

strua.c分配在一个起始于8的整数倍的地址1245032(为什么是这样读者先自己思考,读完就会明白),接下来要在strua.c之后分配strua.d,由于double为8字节,取N=min(8,8),8字节来对齐,所以从strua.c向后找第一个能被8整除的地址,所以取1245032+8得1245040, strua.s为2byte小于参数n,所以N=min(2,8),即N=2,取2字节长度对齐,所以要从strua.d后面寻找第一个能被2整除的地址来存储strua.s,由于strua.d后面的地址为1245048可以被2整除,所以strua.s紧接着分配,现在来分配strua.i,int为4byte,小于指定对齐参数8byte,所以N=min(4,8)取N=4byte对齐,strua.s后面第一个能被4整除地址为1245048+4,所以在1245048+4的位置分配了strua.i,中间补空,同时由于所有成员的N值的最大值为8,所以整个结构长度为8byte的最小整数倍,即取24byte其余均补0.

于是该结构体的对齐参数就是8byte。

 

2)当对齐参数n设置为16byte时,结果同上,不再分析

 

3)当对齐参数设置为4byte时

上例结果为:Len:20

1245036,1245040,1245048,1245052

内存中成员分布如下:

 

Strua.c起始于一个4的整数倍的地址,接下来要在strua.c之后分配strua.d,由于strua.d长度为8byte,大于对齐参数4byte,所以N=min(8,4)取最小的4字节,所以向后找第一个能被4整除的地址来作为strua.d首地址,故取1245036+4,接着要在strua.d后分配strua.s,strua.s长度为2byte小于4byte,取N=min(2,4)2byte对齐,由于strua.d后的地址为1245048可以被2

整除,所以直接在strua.d后面分配,strua.i的长度为4byte,所以取N=min(4,4)4byte对齐,所以从strua.s向后找第一个能被4整除的位置即1245048+4来分配和strua.i,同时N的最大值为4byte,所以整个结构的长度为4byte的最小整数倍16byte

 

4)当对齐参数设置为2byte时

上例结果为:Len:16

1245040,1245042,1245050,1245052

Strua.c分配后,向后找一第一个能被2整除的位置来存放strua.d,依次类推

 

5)1byte对齐时:

上例结果为:Len:15

1245040,1245041,1245049,1245051

此时,N=min(sizeof(成员),1),取N=1,由于1可以整除任何整数,所以各个成员依次分配,没有间空,如下图所示:

6)当结构体成员为数组时,并不是将整个数组当成一个成员来对待,而是将数组的每个元素当一个成员来分配,其他分配规则不变,如将上例的结构体改为:

struct A

{

       char       c;    //1byte

       double   d;    //8byte

       short      s;     //2byte

       char      szBuf[5];     

};

对齐参数设置为8byte,则,运行结果如下:

Len:24

1245032,1245040,1245048,1245050

Strua 的s分配后,接下来分配Strua的数组szBuf[5],这里要单独分配它的每个元素,由于是char类型,所以N=min(1,8),取N=1,所以数组szBuf[5]的元素依次分配没有间隙。

 

7)当结构中有成员不是一个完整的类型单元,如int或short型,而是该类型的一段时,即位段时,如

struct A

{

    int    a1:5;

    int    a2:9;

    char   c;

    int    b:4;

    short  s;

 

};

    对于位段成员,存储是按其类型分配空间的,如int型就分配4个连续的存储单元,如果是相邻的同类型的段位成员就连续存放,共用存储单元,此处如a1,a2将公用一个4字节的存储单元,当该类型的长度不够用时,就另起一个该类型长度的存储空间。有位段时的对齐规则是这样:同类型的、相邻的可连续在一个类型的存储空间中存放的位段成员作为一个该类型的成员变量来对待,不是同类型的、相邻的位段成员,分别当作一个单独得该类型的成员来对待,分配一个完整的类型空间,其长度为该类型的长度,其他成员的分配规则不变,仍然按照前述的对齐规则进行。

对于 struct A,VC++6.0中n设置为8时,sizeof(A)=16,内存分布:

又如:

struct   B    

  {  

  int  a:5;    

  int  b:7;    

  int  c:6;

  int  d:9;

  char e:2;

  int  x;

  }; 

Vc++6.0的对齐参数设置为8、16、4字节对齐时,sizeof(A)=12内存分布为:

(灰色部分未使用)

当对齐参数设置为2字节时:(灰色部分未使用)sizeof(A)=10

 

又如intel的笔试题:

#include      “stdafx.h”  

 #include   <iostream.h>  

 struct   bit  

  {  

int  a:3;

    int   b:2;

    int  c:3;

  };

 int   main(int  argc,   char*  argv[])    

  {    

bit   s;   

 char   *c   =   (char*)&s;    

  *c  =  0x99;    

 cout   <<      s.a   <<endl  <<s.b<<endl<<s.c<<endl;  

 return   0;    

  }

 Output:?  

 运行的结果是   1   -1  -4  

结构bit的成员在内存中由低地址到高地址顺序存放,执行 *c=0x99;后成员的内存分布情况为:

 

8)当结构体成员是结构体类型时,那么该过程是个递归过程,且把该成员作为一个整体来对待,如(微软笔试题):

struct example1

 {

       short a ;

       long b;

 };

 

 struct example2

 {

 char c;

 example1struct1;

 short e;   

 };

 

int main(int argc, char* argv[])

{

       example2e2;

       intd=(unsigned int)&e2.struct1-(unsigned int)&e2.c;

       printf("%d,%d,%d\n",sizeof(example1),sizeof(example2),d);

       return 0;

}

8byte对齐时,结果为:

8,16,4

内存分布为:

因为example1的对齐参数为4,分配完c后要接着分配struct1,这时的对齐参数为min(struct1的对齐参数,指定对齐参数),开始分配struct1,在struct1的成员分配过程中又是按照前述的规则来分配的。

 

关于结构体内存对齐

内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。但是C语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。如果你想了解更加底层的秘密,“内存对齐”对你就不应该再透明了。

一、内存对齐的原因
大部分的参考资料都是如是说的:
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

二、对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。

对齐步骤:
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragmapack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2颗推断:当#pragmapack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
备注:数组成员按长度按数组类型长度计算,如char t[9],在第1步中数据自身长度按1算,累加结构体时长度为9;2步中,找最大数据长度时,如果结构体T有复杂类型成员A的,该A成员的长度为该复杂类型成员A的最大成员长度。

三、试验
我们通过一系列例子的详细说明来证明这个规则吧!
我试验用的编译器包括GCC 3.4.2和VC6.0的C编译器,平台为Windows XP + Sp2。

我们将用典型的struct对齐来说明。首先我们定义一个struct:
#pragma pack(n) /* n = 1, 2, 4, 8, 16 */
struct test_t {
 int a;
 char b;
 short c;
 char d;
};
#pragma pack(n)
首先我们首先确认在试验平台上的各个类型的size,经验证两个编译器的输出均为:
sizeof(char) = 1
sizeof(short) = 2
sizeof(int) = 4

我们的试验过程如下:通过#pragma pack(n)改变“对齐系数”,然后察看sizeof(struct test_t)的值。

1、1字节对齐(#pragma pack(1))
输出结果:sizeof(struct test_t) = 8[两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(1)
struct test_t {
 int a;  /* 长度4 < 1 按1对齐;起始offset=00%1=0;存放位置区间[0,3] */
 char b;  /* 长度1 = 1 按1对齐;起始offset=44%1=0;存放位置区间[4] */
 short c; /* 长度2 > 1 按1对齐;起始offset=55%1=0;存放位置区间[5,6] */
 char d;  /* 长度1 = 1 按1对齐;起始offset=77%1=0;存放位置区间[7] */
};
#pragma pack()
成员总大小=8

2) 整体对齐
整体对齐系数 =min((max(int,short,char), 1) = 1
整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 =8 /* 8%1=0 */ [注1]

2、2字节对齐(#pragma pack(2))
输出结果:sizeof(struct test_t) = 10[两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(2)
struct test_t {
 int a;  /* 长度4 > 2 按2对齐;起始offset=00%2=0;存放位置区间[0,3] */
 char b;  /* 长度1 < 2 按1对齐;起始offset=44%1=0;存放位置区间[4] */
 short c; /* 长度2 = 2 按2对齐;起始offset=66%2=0;存放位置区间[6,7] */
 char d;  /* 长度1 < 2 按1对齐;起始offset=88%1=0;存放位置区间[8] */
};
#pragma pack()
成员总大小=9

2) 整体对齐
整体对齐系数 =min((max(int,short,char), 2) = 2
整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 =10 /* 10%2=0 */

3、4字节对齐(#pragma pack(4))
输出结果:sizeof(struct test_t) = 12[两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(4)
struct test_t {
 int a;  /* 长度4 = 4 按4对齐;起始offset=00%4=0;存放位置区间[0,3] */
 char b;  /* 长度1 < 4 按1对齐;起始offset=44%1=0;存放位置区间[4] */
 short c; /* 长度2 < 4 按2对齐;起始offset=66%2=0;存放位置区间[6,7] */
 char d;  /* 长度1 < 4 按1对齐;起始offset=88%1=0;存放位置区间[8] */
};
#pragma pack()
成员总大小=9

2) 整体对齐
整体对齐系数 =min((max(int,short,char), 4) = 4
整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 =12 /* 12%4=0 */

4、8字节对齐(#pragma pack(8))
输出结果:sizeof(struct test_t) = 12[两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(8)
struct test_t {
 int a;  /* 长度4 < 8 按4对齐;起始offset=00%4=0;存放位置区间[0,3] */
 char b;  /* 长度1 < 8 按1对齐;起始offset=44%1=0;存放位置区间[4] */
 short c; /* 长度2 < 8 按2对齐;起始offset=66%2=0;存放位置区间[6,7] */
 char d;  /* 长度1 < 8 按1对齐;起始offset=88%1=0;存放位置区间[8] */
};
#pragma pack()
成员总大小=9

2) 整体对齐
整体对齐系数 =min((max(int,short,char), 8) = 4
整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 =12 /* 12%4=0 */


5、16字节对齐(#pragma pack(16))
输出结果:sizeof(struct test_t) = 12[两个编译器输出一致]
分析过程:
1) 成员数据对齐
#pragma pack(16)
struct test_t {
 int a;  /* 长度4 < 16 按4对齐;起始offset=00%4=0;存放位置区间[0,3] */
 char b;  /* 长度1 < 16 按1对齐;起始offset=44%1=0;存放位置区间[4] */
 short c; /* 长度2 < 16 按2对齐;起始offset=66%2=0;存放位置区间[6,7] */
 char d;  /* 长度1 < 16 按1对齐;起始offset=88%1=0;存放位置区间[8] */
};
#pragma pack()
成员总大小=9

2) 整体对齐
整体对齐系数 =min((max(int,short,char), 16) = 4
整体大小(size)=$(成员总大小) 按 $(整体对齐系数) 圆整 =12 /* 12%4=0 */

 

记录类型的内存分配!

Packed Record和Record的不同之处!

type

MyRec=Record

var1:integer;

var2,var3,var4,var5,var6,var7,var8:shortint;

var9:integer;

var10:shortint;

var11:integer;

var12,var13:shortint;

end;

...

ShowMessage(intTostr(SizeOf(MyRec)));

结果显示为18,而按我想象应为16。请高手讲解一下Delphi5.0中变量内存空间分配机制,因为我有一个数组MyArray:Array[1..1000000]of MyRec;需要考虑节省内存问题,

另外不要说我懒不爱看书,我手头所有关于Delphi的书都没有提到这个问题。

回答:

显示的结果应该为28,而不是18!按道理应该是22。用Packed的结果就是22。

拟定义的数组比较大,应该用packed record!

原因如下:

在Windows中内存的分配一次是4个字节的。而Packed按字节进行内存的申请和分配,这样速度要慢一些,因为需要额外的时间来进行指针的定位。因此如果不用Packed的话,Delphi将按一次4个字节的方式申请内存,因此如果一个变量没有4个字节宽的话也要占4个字节!这样就浪费了。按上面的例子来说:

var1:integer;//integer刚好4个字节!

var2-var5占用4个字节,Var6-Var8占用4个字节,浪费了一个字节。

var9:integer//占用4个字节;

var10:占用4个字节;浪费3个字节

var11:占用4个字节;

var12,var13占用4个字节;浪费2个字节

所以,如果不用packed的话,那么一共浪费6个字节!所以原来22个字节的记录需要28个字节的内存空间!

****************

回复人:eDRIVE(eDRIVE) (2001-3-2 17:45:00) 得0分

这是因为在32位的环境中,所有变量分配的内存都进行“边界对齐”造成的。这样做可以对速度有优化作用;但是单个定义的变量至少会占用32位,即4个字节。所以会有长度误差,你可以用packed关键字取消这种优化。

深入的分析,内存空间(不是内存地址)在计算机中划分为无数与总线宽度一致的单位,单位之间相接的地方称为“边界”;总线在对内存进行访问时,每次访问周期只能读写一个单位(32bit),如果一个变量横跨“边界”的话,则读或写这个变量就得用两个访问周期,而“边界对齐”时,只需一个访问周期,速度当然会有所优化。

Record的数据各个字节都是对齐的,数据格式比较完整,所以这种格式相对packed占用的内存比较大,

但是因为格式比较整齐,所以电脑读取这个类型的数据的时候速度比较快。

而Packed Record对数据进行了压缩,节省了内存空间,当然他的速度也变的慢了。

   type  

       //    Declare   an    unpacked    record  

      TDefaultRecord    =    Record  

           name1        :   string[4];  

           floater    :   single;  

           name2        :   char;  

           int            :   Integer;  

       end;  

       //    Declare   a    packed    record  

      TPackedRecord    =    Packed   Record  

           name1        :   string[4];  

           floater    :   single;  

           name2        :   char;  

           int            :   Integer;  

       end;  

   var  

       defaultRec    :   TDefaultRecord;  

       packedRec      :   TPackedRecord;  

   begin  

      ShowMessage('Default   record    size    =   '+IntToStr(SizeOf(defaultRec)));  

      ShowMessage('Packed    record    size   =    '+IntToStr(SizeOf(packedRec)));  

   end;  

     Default    record   size    =    20  

     Packed    record   size    =    14  

不过,对于现在的操作系统来,packed Record 节省的那些空间已不用考虑他了。除了做DLL(不用packed容易造成内存混乱)和做硬件编程时(比如串口)编程时必须用到packed Record,其它情况都可以用Record

C的结构体与Delphi中的记录类型

 

Object Pascal的指针
    一、类型指针的定义。对于指向特定类型的指针,在C中是这样定义的:
        int *ptr;
        char *ptr;
        与之等价的Object Pascal是如何定义的呢?
        var
        ptr : ^Integer;
        ptr : ^char;
        其实也就是符号的差别而已。

    二、无类型指针的定义。C中有void *类型,也就是可以指向任何类型数据的指针。Object Pascal为其定义了一个专门的类型:Pointer。于是,
        ptr : Pointer;
        就与C中的
        void *ptr;
        等价了。

    三、指针的解除引用。要解除指针引用(即取出指针所指区域的值),C 的语法是 (*ptr),Object Pascal则是 ptr^。

    四、取地址(指针赋值)。取某对象的地址并将其赋值给指针变量,C 的语法是
        ptr = &Object;
        Object Pascal 则是
        ptr := @Object;
        也只是符号的差别而已。

    五、指针运算。在C中,可以对指针进行移动的运算,如:
        char a[20];  
        char *ptr=a;  
        ptr++;
        ptr+=2;
        当执行ptr++;时,编译器会产生让ptr前进sizeof(char)步长的代码,之后,ptr将指向a[1]。ptr+=2;这句使得ptr前进两个sizeof(char)大小的步长。同样,我们来看一下Object Pascal中如何实现:
        var
            a : array [1..20] of Char;
            ptr : PChar; //PChar 可以看作 ^Char
        begin
            ptr := @a;
            Inc(ptr); // 这句等价于 C 的 ptr++;
            Inc(ptr, 2); //这句等价于 C 的 ptr+=2;
        end;

    六、动态内存分配。C中,使用malloc()库函数分配内存,free()函数释放内存。如这样的代码:
        int *ptr, *ptr2;
        int i;
        ptr = (int*) malloc(sizeof(int) * 20);
        ptr2 = ptr;
        for (i=0; i<20; i++){
            *ptr = i;
            ptr++;
        }
        free(ptr2);
        Object Pascal中,动态分配内存的函数是GetMem(),与之对应的释放函数为FreeMem()(传统Pascal中获取内存的函数是New()和 Dispose(),但New()只能获得对象的单个实体的内存大小,无法取得连续的存放多个对象的内存块)。因此,与上面那段C的代码等价的Object Pascal的代码为:
        var ptr, ptr2 : ^integer;
            i : integer;
        begin
            GetMem(ptr, sizeof(integer) * 20);
                //这句等价于C的 ptr = (int*) malloc(sizeof(int) * 20);
            ptr2 := ptr; //保留原始指针位置
            for i := 0 to 19 do
            begin
                ptr^ := i;
                Inc(ptr);
            end;
            FreeMem(ptr2);
        end;
        对于以上这个例子(无论是C版本的,还是Object Pascal版本的),都要注意一个问题,就是分配内存的单位是字节(BYTE),因此在使用GetMem时,其第二个参数如果想当然的写成 20,那么就会出问题了(内存访问越界)。因为GetMem(ptr, 20);实际只分配了20个字节的内存空间,而一个整形的大小是四个字节,那么访问第五个之后的所有元素都是非法的了(对于malloc()的参数同样)。

    七、字符数组的运算。C语言中,是没有字符串类型的,因此,字符串都是用字符数组来实现,于是也有一套str打头的库函数以进行字符数组的运算,如以下代码:
        char str[15];
        char *pstr;
        strcpy(str, "teststr");
        strcat(str, "_testok");
        pstr = (char*) malloc(sizeof(char) * 15);
        strcpy(pstr, str);
        printf(pstr);
        free(pstr);
        而在Object Pascal中,有了String类型,因此可以很方便的对字符串进行各种运算。但是,有时我们的Pascal代码需要与C的代码交互(比如:用Object Pascal的代码调用C写的DLL或者用Object Pascal写的DLL准备允许用C写客户端的代码)的话,就不能使用String类型了,而必须使用两种语言通用的字符数组。其实,Object Pascal提供了完全相似C的一整套字符数组的运算函数,以上那段代码的Object Pascal版本是这样的:
        var str : array [1..15] of char;
            pstr : PChar; //Pchar 也就是 ^Char
        begin
            StrCopy(@str, 'teststr'); //在C中,数组的名称可以直接作为数组首地址指针来用
                                      //但Pascal不是这样的,因此 str前要加上取地址的运算符
            StrCat(@str, '_testok');
            GetMem(pstr, sizeof(char) * 15);
            StrCopy(pstr, @str);
            Write(pstr);
            FreeMem(pstr);
        end;

    八、函数指针。在动态调用DLL中的函数时,就会用到函数指针。假设用C写的一段代码如下:
        typedef int (*PVFN)(int); //定义函数指针类型
        int main()
        {
            HMODULE hModule = LoadLibrary("test.dll");
     PVFN pvfn = NULL;
            pvfn = (PVFN) GetProcAddress(hModule, "Function1");
            pvfn(2);
            FreeLibrary(hModule);
        }
        就我个人感觉来说,C语言中定义函数指针类型的typedef代码的语法有些晦涩,而同样的代码在Object Pascal中却非常易懂:
        type PVFN = Function (para : Integer) : Integer;
        var
            fn : PVFN;
                //也可以直接在此处定义,如:fn : function (para:Integer):Integer;
            hm : HMODULE;
        begin
            hm := LoadLibrary('test.dll');
            fn := GetProcAddress(hm, 'Function1');
            fn(2);
            FreeLibrary(hm);
        end;

以上是一位Delphi高手给我回的贴!


  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值