C语言:计算结构体偏移量的一个小技巧

一. 概述

经常会遇到计算结构体偏移量的需求, 比如有下面这样一个结构体:

struct mav_protocol
{
    char       header;
    char       seq;
    short      command_id;
    char       payload[256];
    int        crc32;
} p;

需要在传输到对端前填入它的crc32值,以确保对端在收到这组数据后能够根据填入的crc32值判断收到的这组数据是否仍然正确。

那一般都会有一个公共的函数去计算结构体里某段数据的crc值,函数原型大概如下:

int get_crc32(char *buf, int size)
{
    // 计算CRC...    
    return 0;
}

需要传入一个数据指针以及需要计算数据的偏移量,对于我们这个例子来说,需要传入的数据指针就是  struct mav_protocol * , 偏移量就是结构体里crc32这个成员之前的所有数据的长度,也就是 header, seq, command_id...., 等等数据需要参与crc计算。一般计算偏移量的做法是 :

int offset = (unsigned char *)&p.crc32 - (unsigned char *)&p;

然后再代入上面的那个计算crc32的函数:

get_crc32((char *)&p, offset);

今天发现了另一种更巧妙的写法:

二. 具体写法

我看到这哥们直接定义了一个宏:

#define offset(type, v) (&(((type *)0)->v))

然后在使用时直接使用  offset(struct mav_protocol, crc32)  来拿到 crc32这个结构体成员在结构中的偏移量,刚开始很疑惑这种写法,后来反复看了几次之后明白了其中的妙处:

简单的说,既然结构体成员的地址减去结构体的地址就等于该成员的偏移量,那如果结构体的地址为0,该成员的地址就恰好等于它在结构体中的偏移量了。

下面做一个实验来验证这个写法是否正确:

#include <stdio.h>
#include <string.h>

#define offset(type, v) (&(((type *)0)->v))

int get_crc32(char *buf, int size);

struct mav_protocol
{
    char       header;
    char       seq;
    short      command_id;
    char       payload[256];
    int        crc32;
} p;


int main()
{

    printf("struct p's address is: 0x%x\n", &p);    
    printf("header field's address id: 0x%x\n", &p.header);

    printf("---------------------------------\n");

    printf("crc32 field's address: 0x%x\n", &p.crc32);

    int offset = (unsigned char *)&p.crc32 - (unsigned char *)&p;

    printf("offset is: %d\n", offset);

    printf("-----------------------------------------------------------\n");

    printf("crc32 offset: %d\n", offset(struct mav_protocol, crc32));

    return 0;
}

运行:

struct p's address is: 0x407040

header field's address id: 0x407040

---------------------------------

crc32 field's address: 0x407144

offset is: 260

-----------------------------------------------------------

crc32 offset: 260

可以看到,不管是使用普通的做法还是使用宏的做法得到的结果都是一致的,这样以后需要计算任意结构体成员的偏移量都可以通过这个宏,只传一个结构名和一个结构体成员名就可以了。

而且,无论以后如何调整这个结构体的成员,删除也好,新增也罢,只要保证crc32是它的最后一个成员,计算校验值的代码就无需改动,这样的C语言代码维护起来也是非常省心的。

 
posted @ 2021-03-04 15:09  夜行过客  阅读(1887)  评论(0编辑  收藏  举报