C语言结构体4字节对齐对效率的影响

在定义通信协议时,结构体一般是按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的整数倍(上面已经说了)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值