offsetof和container_of

背景:在项目中,我将设备的配置信息封装在一个结构体中,并用一个unsigned int的变量上的某些位标志来对应某些配置项,该结构体需要被保存在本地配置文件中,程序启动时需要加载配置文件上记录的该结构体的配置信息,若不存在则将该结构体默认配置信息写入文件。在程序运行期间,若用户改变了某些配置,标志变量对应的标志位被置1,程序在得知发生变化的标志量后就将对应的配置信息更新到配置文件中。

  假设该结构体的原型如下:

typedef DevCfgInfo
{
    int Addr;
    char ConnectionStatus;
    char LoadName[NAME_SIZE];
}DevCfgInfo;

  程序运行期间,若用户改变了某个变量的值,将结构体中的某个配置项更新到配置文件对应的位置:
  (1)计算该变量偏移了结构体开头n个字节;
  (2)调用系统调用fseek()将文件指针定位到n个字节处理;
  (3)调用fwrite()函数更新该变量信息。
  其中在配置信息发生改变的变量具体结构体开头偏移的字节数计算方法如下:

#define NAME_SIZE 24

typedef struct 
{
    int Addr;
    char ConnectionStatus;
    char LoadName[NAME_SIZE];
}DevCfgInfo;

int main(void)
{
    DevCfgInfo info = {
        .Addr = 1,
        .ConnectionStatus = 1,
        .LoadName = "helloworld",
    };

    //printf("%d-%d-%s\n", info.Addr, info.ConnectionStatus, info.LoadName);

    //将地址值强制转换为单字节的char型,相减后得到二者之间相差几个字节
    printf("info.Addr offset %d Byte\n", (char*)&(info.Addr) - (char*)(&info));
    printf("info.ConnectionStatus offset %d Byte\n", (char*)&(info.ConnectionStatus) - (char*)(&info));
    printf("info.LoadName offset %d Byte\n", (char*)&(info.LoadName) - (char*)(&info));

    return 0;
}

  编译运行:
这里写图片描述
  在Linux内核代码中的offsetof宏定义同样是要实现求结构体成员距离结构体开头偏移的字节数的需求,其原型如下:

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

  &((TYPE *)0)->MEMBER是将0强制转为TYPE类型的地址,在0地址的基础通过->操作符找到MEMBER成员,进而对其取址操作。因为MEMBER是从0地址处开始偏移的,所以对MEMBER取值得到的就是MEMBER在结构体开始处的偏移值。
  注意,若打印((TYPE )0)->MEMBER或者为((TYPE )0)->MEMBER赋值都是在访问该地址上的内容,那么会产生段错误,但是&((TYPE *)0)->MEMBER是取址操作,并不涉及内容访问,所以不会报错:

printf("%d\n", (size_t)(((DevCfgInfo*)0)->ConnectionStatus));       //访问0x04地址上的内容,段错误
printf("%d\n", (size_t)(&(((DevCfgInfo*)0)->ConnectionStatus)));    //ok

  offsetof的使用:向offsetof宏传入结构体类型和具体成员遍历即可知道该变量在所在的结构体变量的偏移量,

int main(void)
{
    DevCfgInfo info = {
        .Addr = 1,
        .ConnectionStatus = 1,
        .LoadName = "helloworld",
    };

    printf("info.Addr offset %d Byte\n", offsetof(DevCfgInfo, Addr));
    printf("info.ConnectionStatus offset %d Byte\n", offsetof(DevCfgInfo, ConnectionStatus));
    printf("info.LoadName offset %d Byte\n", offsetof(DevCfgInfo, LoadName));   

    return 0;
}

  编译运行:
这里写图片描述
  offsetof宏是用来获取成员变量在所在结构体变量的偏移值,containter_of则是通过成员变量获取所在结构体变量的起始地址:

#define container_of(ptr, type, member) ({              \
const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
(type *)( (char *)__mptr - offsetof(type,member) );})

  (1)({})是GNU c编译器的语法扩展,标准c编译器是不支持的;({})和逗号表达式类似,结果为最后一个语句的值:

int a = 0;
int b = 0;
int r = ( a = 1, b = 2, a + b );

  等价于

int r = ({int a = 1, int b = 2, a + b});

  因为在逗号表达式中不能定义变量,所以该宏采用({})的写法。
  (2)typeof是GNC c编译器特有的关键字,它只在编译期间生效,用于获得变量的类型。

int a = 100;
typeof(i) j = a;            //int j = a;
const typeof(i)* p = &j;    //const int* p = &j;

  const typeof( ((type *)0)->member ) *__mptr = (ptr)即通过typeof关键字获取member成员的类型,并定义该类型的const指针,用于保存member的地址真实地址ptr。
  (3)(type*)((char*)__mptr - offsetof(type, member)):offsetof宏如上所述,获取member距离结构体变量偏移的量,那么通过__mptr减去该偏移量即可获得结构体的起始地址。
以通过info.LoadName[0]的地址获取info的起始地址例,图解如下:
这里写图片描述
  从图可见,求info的起始地址可以不定义__mptr,直接通过ptr - offsetof就可以获得,即可改写container_of为:

#define m_container_of(ptr, type, member) ((type *)( (char *)ptr - offsetof(type,member)))

  这样一来还可以不使用({})的扩展语法。

#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#define m_container_of(ptr, type, member) ((type *)( (char *)ptr - offsetof(type,member)))

#define container_of(ptr, type, member) ({                  \
        const typeof(((type*)0)->member)* __mptr = (ptr);   \
        (type*)((char*)__mptr - offsetof(type, member)); })

int main(void)
{
    DevCfgInfo info = {
        .Addr = 1,
        .ConnectionStatus = 1,
        .LoadName = "helloworld",
    };

    printf("--&info = %p\n", &info);
    printf("2. &info = %p\n", m_container_of(&info.LoadName[0], DevCfgInfo, LoadName[0]));
    printf("3. &info = %p\n", container_of(&info.LoadName[0], DevCfgInfo, LoadName[0]));    

    return 0;
}

  运行:
这里写图片描述
  其实定义__mptr指针的意义在于类型检查–检查ptr指针类型是否等于member成员的类型。

`
double m = 0;
printf("-- &info = %p\n", &info);
printf("1. &info = %p\n", m_container_of(&m, DevCfgInfo, LoadName[0]));
printf("2. &info = %p\n", container_of(&m, DevCfgInfo, LoadName[0]));

  编译时,对container_of产生类型不兼容警告:
这里写图片描述`

猜你喜欢

转载自blog.csdn.net/qq_29344757/article/details/79253549