内存对齐小结(关于计算结构体大小和内存对齐)

1.#pragma pack(push,1)与#pragma pack(1)的区别,及工程使用举例

  • pragma的作用:

注:若不用#pragma pack(1)和#pragma pack()括起来,则sample按编译器默认方式对齐(成员中size最大的那个)。即按8字节(double)对齐,则sizeof(sample)==16.成员char a占了8个字节(其中7个是空字节);若用#pragma pack(1),则sample按1字节方式对齐sizeof(sample)==9.(无空字节),比较节省空间啦,有些场和还可使结构体更易于控制。

1)方式一 #pragma pack (n)

  • 代码举例
#pragma pack (n)             作用:C编译器将按照n个字节对齐。

--结构体巴拉巴拉小魔仙

#pragma pack ()               作用:取消自定义字节对齐方式。

2)方式二 #pragma pack(push , 1)

  • 代码
#pragma  pack (push,1)     作用:是指把原来对齐方式设置压栈,并设新的对齐方式设置为一个字节对齐

--结构体巴拉巴拉小魔仙

#pragma pack(pop)            作用:恢复对齐状态

3)两者区别

因此可见,加入push和pop可以使对齐恢复到原来状态,而不是编译器默认,可以说后者更优,但是很多时候两者差别不大

4)应用举例

·说明
若不用#pragma pack(1)和#pragma pack()括起来,则sample按编译器默认方式对齐(成员中size最大的那个)。即按8字节(double)对齐,则sizeof(sample)==16.成员char a占了8个字节(其中7个是空字节);若用#pragma pack(1),则sample按1字节方式对齐sizeof(sample)==9.(无空字节),比较节省空间啦,有些场和还可使结构体更易于控制

#pragma pack(1) // 按照1字节方式进行对齐
struct TCPHEADER 
{
    
    
     short SrcPort; // 16位源端口号
     short DstPort; // 16位目的端口号
     int SerialNo; // 32位序列号
     int AckNo; // 32位确认号
     unsigned char HaderLen : 4; // 4位首部长度
     unsigned char Reserved1 : 4; // 保留6位中的4位
     unsigned char Reserved2 : 2; // 保留6位中的2位
     unsigned char URG : 1;
     unsigned char ACK : 1;
     unsigned char PSH : 1;
     unsigned char RST : 1;
     unsigned char SYN : 1;
     unsigned char FIN : 1;
     short WindowSize; // 16位窗口大小
     short TcpChkSum; // 16位TCP检验和
     short UrgentPointer; // 16位紧急指针
}; 
#pragma pack()

2.C进阶 结构体 联合体大小计算

1)当没有定义 #pragma pack(N)时,此时对齐数 = Min(编译器默认的对齐数8,sizeof())。
2)当定义了 #pragma pack(N),此时对齐数 = Min(N,sizeof())它的计算规则仍然满足结构体的内存对齐原则。
3)联合体大小的计算:

①当没有定义 #pragma pack(N)这种指定 N字节进行对齐时,它的计算规则是:
联合体中最大成员所占内存的大小且必须为最大类型所占字节的整数倍。
②当定义了 #pragma pack(N),以 N字节进行对齐时,它的计算规则如下:
联合体中最大成员所占字节且必须为N的最小倍数。

3.将0强转为指针的一种用法及工程使用举例

需求

写程序,有时为了方便,需要将0强转为指针,进行操作, 其中一种用法,为用来获取某个成员在对象中的offset, 以方便后续的其它操作

测试代码

 typedef  struct
{
    
    
      int  id;
      int  age;
}people;

//根据 对象中的某一个成员(element)获取其在对象中的偏移
#define offset(obj, element)    (long)(&((obj*)0)->element)
//根据对象中某个成员的地址, 获取其对应对象的地址
#define GET_OBJ_FORM_ELEMENT(p_element,obj,element)       ((obj*)((char *)(p_element) - (long)(&((obj *)0)->element)))
int main()
{
    
    
    people* lili = new people;
    lili->_id = 1;
    lili->_age = 23;
    int offs = offset(people, _age);
    cout << offs <<endl;


    people* hanmeimei = GET_OBJ_FORM_ELEMENT(&(lili->_age), people, _age);
    cout << hanmeimei->_id << " " <<hanmeimei->_age <<endl;
    return 0;
}

输出结果:

4
1 23

  • linux内核怎么做的
/*
 * 选自 linux-2.6.7 内核源码
 * filename: linux-2.6.7/include/linux/stddef.h
 */
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

linux 中是定义了一个宏 offsetof,这个宏接受两个参数,一个 TYPE 代表结构体的类型,另一个 MEMBER 代表结构体中的成员,我们看看后面的宏替换部分发生了什么,先看 ((TYPE *)0) 这个部分,它把 0 这个数字强制转换成 TYPE * 型的指针类型,这样 ((TYPE *)0) 这个整体就相当于一个指针指向了 0 这个地址,不管 0 这个地址是否合法,是否真的有这么一个结构体对象,它都会把以 0 地址为首的一片连续内存当成一个结构体对象操作,那么再看 ((TYPE *)0)->MEMBER 这个部分,((TYPE *)0) 这个指针要取结构体对象中的 MEMBER 成员,因为这只是读内存的操作,并没有写入数据,所以虽说地址不合法,但并不会发生段错误,这样取到 MEMBER 成员后,前面的 & 符就可以对 MEMBER 成员取地址了,刚才我也说了,B - A 的差是偏移量的话,如果 A 等于 0,那么 B 本身就是偏移量,那正好对应现在的情况,((TYPE *)0) 本身就是以 0 地址为首进行操作,那么它取到的 MEMBER 成员所在的地址就是相对于结构体首地址的偏移量,然后再把这个地址强制转换成 size_t 类型,于是该成员的偏移量就得到了。有没有感觉很精妙。

工程应用:通过结构体成员的地址获取结构体变量的地址

DLINK 为一个双循环链表的头指针,任何定义的结构 struct 只要把DLINK作为其一个成员,即可将多个struct 串成双链表(通过操作DLINK),而不需要为每种struct都自己定义prev & next指针来串成链表,方便做到扩展。 同时还可根据&DLINK, 来获取其对应的对象的指针。操作双链表时,也是通过DLINK来操作。一般将DLINK作为对象的第一个成员。

#pragma pack(push,1)
struct RB_TREE_NODE                 //32
{
    
    
    void* ID;                       //8
    union                           //8
    {
    
    
        /* data */
        RB_TREE_NODE*   pNext;
        uint64_t        uColor; 
    };
    RB_TREE_NODE*       pleft;      //8
    RB_TREE_NODE*       pRight;     //8

}RBNode;
#pragma pack(pop)

struct _VMChunk                     //184
{
    
    
    uint64_t    qwTag;              //8
    uint64_t    qwChunkDataSize;    //8
    void*       pChunkData;         //8
    RB_TREE_NODE RBNode;            //32
    char szName[128];               //128字节

};

struct _VMChunk* pChunk = NULL;
pChunk = (_VMChunk*)((uint64_t)(PBNode) - (uint64_t)(&(_VMChunk*)(0))->RBNode);//偏移量24字节

猜你喜欢

转载自blog.csdn.net/weixin_43679037/article/details/120659528