在定义通信协议时,结构体一般是按1字节对齐的,这样可以防止不同平台下默认字节对齐不一致的问题。那常说的非4字节对齐影响效率到底体现在哪呢?
来看下面两个结构:
#prama pack(1)
typedef struct TNoAlign {
char ch1;
char ch2;
char ch3;
short sh4;
}TNoAlign;
typedef struct TAlign {
short sh4;
char ch1;
char ch2;
char ch3;
}TAlign;
#pragma pack()
两个结构体都按1字节对齐,加上了#pragma标签。
先打印一下两个结构体各个成员在内存中的地址:
TNoAlign tNoAl;
TAlign tAl;
tNoAl.ch1 = 1;
tNoAl.ch2 = 2;
tNoAl.ch3 = 3;
tNoAl.sh4 = 4;
tAl.ch1 = 1;
tAl.ch2 = 2;
tAl.ch3 = 3;
tAl.sh4 = 4;
printf("%d, %d, %d, %d\n", (int)((int*)&tNoAl.ch1), (int)((int*)&tNoAl.ch2), (int)((int*)&tNoAl.ch3), (int)((int*)&tNoAl.sh4));
printf("%d, %d, %d, %d\n", (int)((int*)&tAl.sh4), (int)((int*)&tAl.ch1), (int)((int*)&tAl.ch2), (int)((int*)&tAl.ch3));
结果如下:
7338908, 7338909, 7338910, 7338911
7338892, 7338894, 7338895, 7338896
我们展开到结构体上:
typedef struct TNoAlign {
char ch1; //7338908
char ch2; //7338909
char ch3; //7338910
short sh4; //7338911
}TNoAlign;
typedef struct TAlign {
short sh4; //7338892
char ch1; //7338894
char ch2; //7338895
char ch3; //7338896
}TAlign;
当对结构体的某个成员进行赋值或取值操作时,实际上是CPU找到结构体成员对应的地址的数据进行操作,这里有个寻址的过程,32位CPU大多有下面的规则:
CPU只能寻址4或8整除的地址,如果要寻址其他地址,比如0x00000003,则会寻址到0x00000000上取出4字节再找到第3位置上的数据,如果此时的数据比较长,比如有4个数据,那么第一次寻址只能拿到0x00000000取出4字节的最后一个,然后再寻址0x00000004,拿出剩余的3字节,这样就寻址了两次,花费的时间更多了
在本例中,先看TNoAlign ,当取(或者赋值)ch1时,没问题,ch2和ch3都可以取ch1的地址7338908拿到。当取sh4时,问题就来了,只能先取7338908拿到第一个字节,再取7338910拿到第二个字节。
TAlign 就没这个问题了,ch3可以直接拿到!但TAlign如果定义一个结构体数组时,依然有问题,因为整个结构体占5个字节,后面的取值或者赋值成员时,就会面临不对齐的问题,此时TAlign应该改为如下形式:
typedef struct TAlign {
short sh4;
char ch1;
char ch2;
char ch3;
char chReserve[3]; //保留
}TAlign;
这样就凑够了8字节,放到数组中也没有问题了。
看一下不是很准确的测试:
int iCount = 1000000000;
auto ts = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
for (int i = 0; i < iCount; i++) {
tNoAl.ch1 = i;
tNoAl.ch2 = i;
tNoAl.ch3 = i;
tNoAl.sh4 = i;
}
auto te = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
std::cout << "time:" << te - ts << std::endl;
auto ts2 = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
for (int i = 0; i < iCount; i++) {
tAl.ch1 = 1;
tAl.ch2 = 2;
tAl.ch3 = 3;
tAl.sh4 = 4;
}
auto te2 = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
std::cout << "time:" << te2 - ts2 << std::endl;
分别进行简单的赋值操作,最终的时间耗费如下:
time:4385
time:1722
可以看到,第二个效率高了两倍还多!
总结:
1. 结构体内部,int和short等谁宽谁往前放,char不要在中间定义,比如:
typedef struct TAlign {
int
short ;
short;
char ;
char ;
}TAlign;
如果实在想在中间定义char,则在中间添加保留位:
typedef struct TAlign {
int
char
char reserve[3];
short ;
char ;
char ;
}TAlign;
2. 结构体如果要往数组或者缓冲区里放,那么必须整体字节数是4的整数倍(上面已经说了)