导读:
一, 为什么会有字节对齐问题?
字节对齐问题之主观所以存在,我想是源于程序员和CPU对内存数据访问的理解稍有不同。在我们看来,程序对内存数据的访问总是按照其大小,一个字节接着一个字节进行的,比如读取一个char型变量,系统就读去一个字节的内容,读取一个int型变量,系统就读取四个字节的内容:
0|1|2|3|4|5|6|7……
但是事实并非如此。并非所有CPU都是一个一个字节地来访问内存的(现在可能很少有CPU这么做,效率太低了!)。比如,CPU可以按照1,2,4,6, 8,16,32……的粒度来访问内存数据。这被称之为memory access granularity(内存访问粒度/间隔)[1],例如按照4字节来访问内存:
0123|4567|8……
显然,内存访问效率和数据对象的大小以及CPU的内存访问粒度相关。比如我们在32位系统上访问一个4字节int型数据对象,假设这个对象a的起始地址为0×00:
0×00 0×01 0×02 0×03
这时候,a的内存位置处于效率最佳情况下:
访问粒度 CPU访问次数
1字节 4次
2字节 2次
4字节 1次
我们当然希望每次运行程序,支持4字节访问的CPU都可以一次就读/写这个int型数据。但是事实确未必一定是这样。如果a的起始地址为0×01,则:
访问粒度 CPU访问次数
1字节 4次
2字节 3次
4字节 2次
按照2字节访问需要三次:第一次访问0×00~0×01,第二次访问0×02~0×03,第三次访问0×04~0×05,然后把三部分数据拼凑起来。
同理,按照4字节访问需要两次:第一次访问0×00~0×03,第二次访问0×04~0×07,然后把两部分数据拼凑起来。
显然,a的地址不符合一定的要求,就会导致效率损失。
二,X86的字节对齐规则
Kang Su Gatlin在其文章[2]中指出了X86上对齐的原则。32位X86CPU支持1,2,4,8字节的访问。访问L字节(2的整数倍)数据,其地址A满足A MOD L = 0。
这样对基本的数据类型就有如下规则:
char —— 单字节对齐,A MOD 1 = 0;可为任意的奇/偶值
short —— 双字节对齐,A MOD 2 = 0;偶地址
int —— 四字节对齐,A MOD 4 = 0;地址最后一位为:4,8,C
double —— 八字节对齐, A MOD 8 = 0;地址最后一位为:0,8
三,堆栈的对齐布局
先用VC6运行一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
char c2;
printf(”%p/n%p/n%p/n”,&c0,&c1,&c2);
return 0;
}
在Debug模式下,得到类似如下的结果(具体地址值可能不同):
0012FF7C
0012FF78
0012FF74
OK,这个结果的每个地址,都符合1字节对齐,MOD 1 = 0。不过,等等,也符合四字节对齐阿!MOD 4 = 0。堆栈理论上只需要3个字节,这里却用了12个字节。
因为VC6编译器默认对堆栈采用了4字节对齐原则。所以在不进行任何优化的情况下,一个char对象也需要占据4个字节空间。这样做的好处呢?—— 显然,无论如何,1字节和2字节对象都是符合对齐原则的。
用RELEASE模式,把编译选项调整到MAXSPEED或者MAXSIZE,可以得到如下的输出:
0012FF7D
0012FF7E
0012FF7F
这次堆栈中就没有冗余的字节了。访问速度也不会因为字节排列而下降。
对于32位总线而言,我认为8字节对齐可能没有什么太大意义。因为无论如何,8字节数据对象,例如double,都需要两个时钟周期的。这也就是为什么 VC6在32位平台上总是默认4字节对齐的原因:4字节对齐总是兼容单/双字节对齐方式的。关于32位总线上8字节对齐的性能影响,我么在后面的例子中可以看到。
我们可以修改上一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
double d0;
char c2;
printf(”%p/n%p/n%p/n%p/n”,&c0,&d0,&c1,&c2);
return 0;
}
Debug模式下:
0012FF7C
0012FF6C
0012FF78
0012FF74
VC6将堆栈变量的位置做了偏移。double被放在了栈顶。这样做的好处是什么暂时不太清楚。
四,结构体/联合体的布局
Kang Su Gatlin指出了struct/union的布局规则,对于inter-struction/inter-union,对齐规则很简单,即 struct/union中最大的那个alignment,而这个max(alignment),又取决于编译器的设置(用参数/Zpn或者代码中用 #pragma pack(n)),即自身的对齐要求和编译器设置对齐要求二者中的最小值。
4.1 简单的情况
例如:
#include “stdio.h”
struct SA
{
char g;
double k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
输出结果如下:
Size=16
Addr=0012FF70
VC6默认采用8字节对齐,double的对齐要求也是8字节。因此这里输出大小为16,地址按照规则需符合8字节对齐,最后四位为0000。
我们可以加上pack 限制
#include “stdio.h”
#pragma pack(4)
struct SA
{
char g;
double k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
则输出为:
Size=12
Addr=0012FF74
将默认的对齐边界设置为4,则成员g仅占用4个字节,因为k在4字节边界上对齐。由于SA的成员最大对齐值为4,所以整个结构体也4字节对齐。所以地址的最后4位可以被4整除。
4.2 嵌套的结构体成员
看一个例子:
#include “stdio.h”
struct SB
{
int c;
double k;
short b;
};
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
输出为:
Size=40
Addr=0012FF58
从这个例子可以看出嵌套结构体的规则其实也很简单。首先找到SA中成员的最大对齐边界,由于b是结构体类型,因此b的对齐边界取决于SB中的成员最大对齐边界:8。因此,SA中的成员8字节对齐。由于SB中的成员也是8字节对齐的,所以大小为8*5=40。而整个变量a的地址也要符合8字节对齐要求。
再看看用了pack(4)的情况:
#include “stdio.h”
#pragma pack(4)
struct SB
{
int c;
double k;
short b;
};
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
按照分析,SB的最大对齐边界:4,所以SA的对齐边界也是4,这个结果应该输出:
Size=24
Addr=0012FF68
注意pack(n)的限制,作用域从起始位置开始。例如:
#include “stdio.h”
struct SB
{
int c;
double k;
short b;
};
#pragma pack(4)
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
SB按照8字节对齐,SA按照4字节对齐,其成员b整体上满足4字节对齐即可。因此大小是8*3(SB大小)+8(SA的成员g和k的大小):
Size=32
Addr=0012FF60
再看更复杂的例子:
#include “stdio.h”
#pragma pack(4)
struct SB
{
int c;
double k;
short b;
};
#pragma pack(2)
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
SB的对齐边界为4,因此大小为16,SA的对齐边界为2(最小值),因此大小为16+4=20:
Size=20
Addr=0012FF6C
再来看看成员变量位置的颠倒会给struct带来什么结果?
#include “stdio.h”
struct SB
{
int c;
short b;
double k;
};
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
运行结果如下:
Size=32
Addr=0012FF60
变量a的大小变成了32,因为SB中的double成员k的声明被放在了最后。而int成员c和short成员b各占4字节,就可以保证k处于8字节对齐状态。因此,SB的大小为16。再加上SA的另外两个成员:g和k分别占用8个字节,所以得到的Size就为32。同样,地址还是符合8字节对齐边界的要求。
从这个例子也可以看出,通过合理安排结构体成员的声明顺序,可以减少其占用内存的大小。
五,性能
我使用了如下的程序来测试自己对齐给性能带来的影响:
// Test.cpp : Test the performance of data alignment of 1, 2, 4, 8
#include “stdafx.h”
#include
#include
#include
#include
LARGE_INTEGER operator -(LARGE_INTEGER a, LARGE_INTEGER b)
{
LARGE_INTEGER reVal;
if (a.LowPart
class CCountTest
{
public:
CCountTest()
{
dest = new T[128];
origsource = new byte[10000];
}
~CCountTest()
{
delete[] origsource;
delete[] dest;
}
void CountTest(int offset)
{
UINT iters = 99999;
T* source;
LARGE_INTEGER startCount, endCount, freq;
byte* pByte = GetAlign(origsource, offset);
source = (T *) (pByte);
printf(”dest = %p source = %p/n”, dest, source);
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&startCount);
{
for (UINT x = 0; x CountTest;
CountTest.CountTest(1);
CountTest.CountTest(2);
CountTest.CountTest(4);
CountTest.CountTest(8);
return 0;
}
这里我们用double类型进行测试,从一个数组中向另一个数组复制数据,测试环境:
Intel P4 2.8G + 512 DDR + Windows2000
输出结果如下:
dest = 00342080 source = 00342489
elapsed time = 0;111243
dest = 00342080 source = 0034248A
elapsed time = 0;111481
dest = 00342080 source = 0034248C
elapsed time = 0;74270
dest = 00342080 source = 00342488
elapsed time = 0;69388
可以看到,1字节和2字节对齐是,对double数据的存取远比4或者8字节对齐来的低效!但是4字节和8字节对齐似乎没有对double的存取造成多大的效率差异。实事上,实际运行中最后两个时间值得大小关系并不唯一确定,这里只是一次运行的结果。这可能说明,在32位平台上,double类型数据对象的 8字节对齐,其实并没有比4字节对齐来的效率更高。毕竟32位总线不可能一次传输完8个字节的数据。这也符合前面我们提到的栈字节对齐中为何double 型变量并不一定被系统安排为8字节对齐的情况。
六,尾巴
个人觉得,其实大多数情况下,在Windows上开发,我们都不用关心堆栈和结构体的成员声明顺序,以及是否通过填充冗余字节来达到地址对齐。因为编译器通过优化选项可以自动进行处理。即使浪费了一些内存,在大多数应用中,恐怕也无关痛痒。只是不要相当然的用一个常数来替代sizeof函数就好。
另外就是程序移植到其他的平台上时,可能目标平台并不支持非对齐式的数据访问。编译器也不会自动纠正对齐问题。这样一来,如果想编写移植性好的程序,就要非常小心了。
参考文章:
1,Jonathan Rentzsch : Data alignment: Straighten up and fly right (http://www-128.ibm.com/developerworks/library/pa-dalign/ )
2,Kang Su Gatlin :Windows Data Alignment on IPF, x86, and x64 (http://msdn.microsoft.com/library/en-us/dv_vstechart/html/vcconwindowsdataalignmentonipfx86×86-64.asp?frame=true)
续:
MSN的C++ Group里看到一些讨论,关于栈变量放置的问题:
我们可以修改上一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
double d0;
char c2;
printf(”%p/n%p/n%p/n%p/n”,&c0,&d0,&c1,&c2);
return 0;
}
Debug模式下:
0012FF7C
0012FF6C
0012FF78
0012FF74
VC6将堆栈变量的位置做了偏移。double被放在了栈顶。这样做的好处是什么暂时不太清楚。
——–double放在栈顶,因为有些编译器可以将同一类型的变量连续放置(好处仍然不清楚)。如果要强行安排这些变量的放置顺序,可以用一个struct:
struct A{
char c0;
char c1;
double d0;
char c2;
}
这样就OK了,当然前提是不能让编译器优化struct内部的放置顺序。
本文转自
http://www.rojo.com/story/TQ-MmN79NIU_6wcn
一, 为什么会有字节对齐问题?
字节对齐问题之主观所以存在,我想是源于程序员和CPU对内存数据访问的理解稍有不同。在我们看来,程序对内存数据的访问总是按照其大小,一个字节接着一个字节进行的,比如读取一个char型变量,系统就读去一个字节的内容,读取一个int型变量,系统就读取四个字节的内容:
0|1|2|3|4|5|6|7……
但是事实并非如此。并非所有CPU都是一个一个字节地来访问内存的(现在可能很少有CPU这么做,效率太低了!)。比如,CPU可以按照1,2,4,6, 8,16,32……的粒度来访问内存数据。这被称之为memory access granularity(内存访问粒度/间隔)[1],例如按照4字节来访问内存:
0123|4567|8……
显然,内存访问效率和数据对象的大小以及CPU的内存访问粒度相关。比如我们在32位系统上访问一个4字节int型数据对象,假设这个对象a的起始地址为0×00:
0×00 0×01 0×02 0×03
这时候,a的内存位置处于效率最佳情况下:
访问粒度 CPU访问次数
1字节 4次
2字节 2次
4字节 1次
我们当然希望每次运行程序,支持4字节访问的CPU都可以一次就读/写这个int型数据。但是事实确未必一定是这样。如果a的起始地址为0×01,则:
访问粒度 CPU访问次数
1字节 4次
2字节 3次
4字节 2次
按照2字节访问需要三次:第一次访问0×00~0×01,第二次访问0×02~0×03,第三次访问0×04~0×05,然后把三部分数据拼凑起来。
同理,按照4字节访问需要两次:第一次访问0×00~0×03,第二次访问0×04~0×07,然后把两部分数据拼凑起来。
显然,a的地址不符合一定的要求,就会导致效率损失。
二,X86的字节对齐规则
Kang Su Gatlin在其文章[2]中指出了X86上对齐的原则。32位X86CPU支持1,2,4,8字节的访问。访问L字节(2的整数倍)数据,其地址A满足A MOD L = 0。
这样对基本的数据类型就有如下规则:
char —— 单字节对齐,A MOD 1 = 0;可为任意的奇/偶值
short —— 双字节对齐,A MOD 2 = 0;偶地址
int —— 四字节对齐,A MOD 4 = 0;地址最后一位为:4,8,C
double —— 八字节对齐, A MOD 8 = 0;地址最后一位为:0,8
三,堆栈的对齐布局
先用VC6运行一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
char c2;
printf(”%p/n%p/n%p/n”,&c0,&c1,&c2);
return 0;
}
在Debug模式下,得到类似如下的结果(具体地址值可能不同):
0012FF7C
0012FF78
0012FF74
OK,这个结果的每个地址,都符合1字节对齐,MOD 1 = 0。不过,等等,也符合四字节对齐阿!MOD 4 = 0。堆栈理论上只需要3个字节,这里却用了12个字节。
因为VC6编译器默认对堆栈采用了4字节对齐原则。所以在不进行任何优化的情况下,一个char对象也需要占据4个字节空间。这样做的好处呢?—— 显然,无论如何,1字节和2字节对象都是符合对齐原则的。
用RELEASE模式,把编译选项调整到MAXSPEED或者MAXSIZE,可以得到如下的输出:
0012FF7D
0012FF7E
0012FF7F
这次堆栈中就没有冗余的字节了。访问速度也不会因为字节排列而下降。
对于32位总线而言,我认为8字节对齐可能没有什么太大意义。因为无论如何,8字节数据对象,例如double,都需要两个时钟周期的。这也就是为什么 VC6在32位平台上总是默认4字节对齐的原因:4字节对齐总是兼容单/双字节对齐方式的。关于32位总线上8字节对齐的性能影响,我么在后面的例子中可以看到。
我们可以修改上一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
double d0;
char c2;
printf(”%p/n%p/n%p/n%p/n”,&c0,&d0,&c1,&c2);
return 0;
}
Debug模式下:
0012FF7C
0012FF6C
0012FF78
0012FF74
VC6将堆栈变量的位置做了偏移。double被放在了栈顶。这样做的好处是什么暂时不太清楚。
四,结构体/联合体的布局
Kang Su Gatlin指出了struct/union的布局规则,对于inter-struction/inter-union,对齐规则很简单,即 struct/union中最大的那个alignment,而这个max(alignment),又取决于编译器的设置(用参数/Zpn或者代码中用 #pragma pack(n)),即自身的对齐要求和编译器设置对齐要求二者中的最小值。
4.1 简单的情况
例如:
#include “stdio.h”
struct SA
{
char g;
double k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
输出结果如下:
Size=16
Addr=0012FF70
VC6默认采用8字节对齐,double的对齐要求也是8字节。因此这里输出大小为16,地址按照规则需符合8字节对齐,最后四位为0000。
我们可以加上pack 限制
#include “stdio.h”
#pragma pack(4)
struct SA
{
char g;
double k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
则输出为:
Size=12
Addr=0012FF74
将默认的对齐边界设置为4,则成员g仅占用4个字节,因为k在4字节边界上对齐。由于SA的成员最大对齐值为4,所以整个结构体也4字节对齐。所以地址的最后4位可以被4整除。
4.2 嵌套的结构体成员
看一个例子:
#include “stdio.h”
struct SB
{
int c;
double k;
short b;
};
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
输出为:
Size=40
Addr=0012FF58
从这个例子可以看出嵌套结构体的规则其实也很简单。首先找到SA中成员的最大对齐边界,由于b是结构体类型,因此b的对齐边界取决于SB中的成员最大对齐边界:8。因此,SA中的成员8字节对齐。由于SB中的成员也是8字节对齐的,所以大小为8*5=40。而整个变量a的地址也要符合8字节对齐要求。
再看看用了pack(4)的情况:
#include “stdio.h”
#pragma pack(4)
struct SB
{
int c;
double k;
short b;
};
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
按照分析,SB的最大对齐边界:4,所以SA的对齐边界也是4,这个结果应该输出:
Size=24
Addr=0012FF68
注意pack(n)的限制,作用域从起始位置开始。例如:
#include “stdio.h”
struct SB
{
int c;
double k;
short b;
};
#pragma pack(4)
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
SB按照8字节对齐,SA按照4字节对齐,其成员b整体上满足4字节对齐即可。因此大小是8*3(SB大小)+8(SA的成员g和k的大小):
Size=32
Addr=0012FF60
再看更复杂的例子:
#include “stdio.h”
#pragma pack(4)
struct SB
{
int c;
double k;
short b;
};
#pragma pack(2)
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
SB的对齐边界为4,因此大小为16,SA的对齐边界为2(最小值),因此大小为16+4=20:
Size=20
Addr=0012FF6C
再来看看成员变量位置的颠倒会给struct带来什么结果?
#include “stdio.h”
struct SB
{
int c;
short b;
double k;
};
struct SA
{
char g;
SB b;
char k;
};
int main(int argc, char* argv[])
{
SA a;
printf(”Size=%d/nAddr=%p/n”,sizeof(a),&a);
return 0;
}
运行结果如下:
Size=32
Addr=0012FF60
变量a的大小变成了32,因为SB中的double成员k的声明被放在了最后。而int成员c和short成员b各占4字节,就可以保证k处于8字节对齐状态。因此,SB的大小为16。再加上SA的另外两个成员:g和k分别占用8个字节,所以得到的Size就为32。同样,地址还是符合8字节对齐边界的要求。
从这个例子也可以看出,通过合理安排结构体成员的声明顺序,可以减少其占用内存的大小。
五,性能
我使用了如下的程序来测试自己对齐给性能带来的影响:
// Test.cpp : Test the performance of data alignment of 1, 2, 4, 8
#include “stdafx.h”
#include
#include
#include
#include
LARGE_INTEGER operator -(LARGE_INTEGER a, LARGE_INTEGER b)
{
LARGE_INTEGER reVal;
if (a.LowPart
class CCountTest
{
public:
CCountTest()
{
dest = new T[128];
origsource = new byte[10000];
}
~CCountTest()
{
delete[] origsource;
delete[] dest;
}
void CountTest(int offset)
{
UINT iters = 99999;
T* source;
LARGE_INTEGER startCount, endCount, freq;
byte* pByte = GetAlign(origsource, offset);
source = (T *) (pByte);
printf(”dest = %p source = %p/n”, dest, source);
QueryPerformanceFrequency(&freq);
QueryPerformanceCounter(&startCount);
{
for (UINT x = 0; x CountTest;
CountTest.CountTest(1);
CountTest.CountTest(2);
CountTest.CountTest(4);
CountTest.CountTest(8);
return 0;
}
这里我们用double类型进行测试,从一个数组中向另一个数组复制数据,测试环境:
Intel P4 2.8G + 512 DDR + Windows2000
输出结果如下:
dest = 00342080 source = 00342489
elapsed time = 0;111243
dest = 00342080 source = 0034248A
elapsed time = 0;111481
dest = 00342080 source = 0034248C
elapsed time = 0;74270
dest = 00342080 source = 00342488
elapsed time = 0;69388
可以看到,1字节和2字节对齐是,对double数据的存取远比4或者8字节对齐来的低效!但是4字节和8字节对齐似乎没有对double的存取造成多大的效率差异。实事上,实际运行中最后两个时间值得大小关系并不唯一确定,这里只是一次运行的结果。这可能说明,在32位平台上,double类型数据对象的 8字节对齐,其实并没有比4字节对齐来的效率更高。毕竟32位总线不可能一次传输完8个字节的数据。这也符合前面我们提到的栈字节对齐中为何double 型变量并不一定被系统安排为8字节对齐的情况。
六,尾巴
个人觉得,其实大多数情况下,在Windows上开发,我们都不用关心堆栈和结构体的成员声明顺序,以及是否通过填充冗余字节来达到地址对齐。因为编译器通过优化选项可以自动进行处理。即使浪费了一些内存,在大多数应用中,恐怕也无关痛痒。只是不要相当然的用一个常数来替代sizeof函数就好。
另外就是程序移植到其他的平台上时,可能目标平台并不支持非对齐式的数据访问。编译器也不会自动纠正对齐问题。这样一来,如果想编写移植性好的程序,就要非常小心了。
参考文章:
1,Jonathan Rentzsch : Data alignment: Straighten up and fly right (http://www-128.ibm.com/developerworks/library/pa-dalign/ )
2,Kang Su Gatlin :Windows Data Alignment on IPF, x86, and x64 (http://msdn.microsoft.com/library/en-us/dv_vstechart/html/vcconwindowsdataalignmentonipfx86×86-64.asp?frame=true)
续:
MSN的C++ Group里看到一些讨论,关于栈变量放置的问题:
我们可以修改上一个例子:
#include
int main(int argc, char* argv[])
{
char c0;
char c1;
double d0;
char c2;
printf(”%p/n%p/n%p/n%p/n”,&c0,&d0,&c1,&c2);
return 0;
}
Debug模式下:
0012FF7C
0012FF6C
0012FF78
0012FF74
VC6将堆栈变量的位置做了偏移。double被放在了栈顶。这样做的好处是什么暂时不太清楚。
——–double放在栈顶,因为有些编译器可以将同一类型的变量连续放置(好处仍然不清楚)。如果要强行安排这些变量的放置顺序,可以用一个struct:
struct A{
char c0;
char c1;
double d0;
char c2;
}
这样就OK了,当然前提是不能让编译器优化struct内部的放置顺序。
本文转自
http://www.rojo.com/story/TQ-MmN79NIU_6wcn