• 正文
  • 相关推荐
申请入驻 产业图谱

C语言结构体对齐是怎么计算

18小时前
319
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

在C/C++开发中,结构体是一种常用的数据结构形式,在某些应用场景中,需要特别关注结构体对齐问题。

本篇就来通过一个实际例子,来探究结构体对齐的具体表现以及结构体对齐应该怎么计算。

1 结构体对齐问题

举个例子,一个结构体中,有多个成员,那结构体的整体空间占用大小,等于各个成员大小的累加和吗?再进一步,结构体套结构体,最外面结构体的整体大小,等于各个成员结构体大小的累加和吗?

这就涉及到了结构体对齐问题,如果结构体没对齐,就会出现累加和和整体的大小不一样的情况,那这些对齐具体是怎样的情况,就是本篇要讨论的。

在写代码之前,先来简单介绍下sizeof与offsetof。

1.1 sizeof

sizeof 是一个C 语言关键字/运算符,用于计算某个数据类型、变量或表达式在内存中占用的总字节数,编译时就会确定结果,无运行时开销。

sizeof 结果类型是 size_t,无符号整数,打印用 %zu 格式符结构体的 sizeof 结果会包含内存填充(padding),也就是

结构体对齐

数组名用 sizeof 会计算整个数组的字节数(sizeof(name[10])=10),而数组名传参后会退化为指针,sizeof(指针)的大小就是4(32位系统)或8(64位系统)

1.2 offsetof

offsetof 是 <stddef.h> 中定义的,用于计算结构体中某个成员相对于结构体起始地址的字节偏移量,即该成员距离结构体开头有多少字节。因为是宏,也是编译时计算

sizeof offsetof
本质 关键字(运算符) 宏(基于地址计算)
作用 计算 “总字节数”(含填充) 计算 “成员相对于结构体开头的偏移”
适用对象 类型、变量、表达式 仅结构体 / 联合体的成员
结果含义 占用的总内存大小 成员的内存偏移位置
依赖头文件 无需(内置关键字) 必须包含 <stddef.h>

2 代码实测

2.1 实例代码-整体sizeof与累加sizeof

首先定义3个结构体,每个结构体包含一些自定义的变量:

    DataA_tDataB_tDataC_t

然后再定义一个大的结构DataAll_t,包含DataA_t、DataB_t、DataC_t,外加一个char数组的data_d

为了便于通过循环的方式来展示结构体成员的大小,这里定义了一个StructMemberInfo_t结构:

char *name:结构体成员的名称,需要手动填入

size_t offset:结构体成员的偏移量,使用offsetof计算

size_t size:结构体成员的大小,使用sizeof计算

将所有成员的信息,写入结构体数组后,就可以进行循环打印展示,在循环的过程中,计算各个成sizeof的累加和,最后和整体的sizeof进行对比,观察是否大小一致。

// gcc 1_calc_size.c -o 1_calc_size
#include <stdio.h>
#include <stddef.h>

typedefstruct{
    int   m1;
    float m2;
    char  m3[2];
}DataA_t;

typedefstruct{
    char   n1[3];
    double n2;
}DataB_t;

typedefstruct{
    char x1;
    char x2[2];
}DataC_t;

typedefstruct{
    DataA_t data_a;
    DataB_t data_b;
    DataC_t data_c;
    char    data_d[1];
}DataAll_t;

typedefstruct{
    char * name;
    size_t offset;
    size_t size;
}StructMemberInfo_t;

void show_struct_member_size_info(DataAll_t *data)
{
    StructMemberInfo_t info[] = {
        {"data_a", offsetof(DataAll_t, data_a), sizeof(data->data_a)},
        {"data_b", offsetof(DataAll_t, data_b), sizeof(data->data_b)},
        {"data_c", offsetof(DataAll_t, data_c), sizeof(data->data_c)},
        {"data_d", offsetof(DataAll_t, data_d), sizeof(data->data_d)},
    };
    
    int num = sizeof(info) / sizeof(info[0]);
    size_t sum_size = 0;
    
    size_t total_size = sizeof(DataAll_t);
    printf("datap:%pt total_size:%zun", (void *)data, total_size);
    
    for (int i = 0; i < num; i++)
    {
        void *ptr = (void *)data + info[i].offset;
        sum_size += info[i].size;
        printf("[%d] p:%pt name:%st offset:%zut size:%zut sum_size:%zun",
            i, ptr, info[i].name, info[i].offset, info[i].size, sum_size);
    }
    
    if (sum_size == total_size)
    {
        printf("struct data member size check ok, sum_size:%zu, total_size:%zun", sum_size, total_size);
    }
    else
    {
        printf("struct data member size check fail, sum_size:%zu, total_size:%zun", sum_size, total_size);
    }
}

int main()
{
    DataAll_t data_all;
    
    show_struct_member_size_info(&data_all);
    
    return0;
}

运行结果如下,可以看到是不一致的,说明存在字节对齐的现象:

2.2 增加结构体成员的地址打印

那具体是怎样的对齐,我们可以把各个成员的地址打印出来:

show_struct_member_addr

    • :将成员的地址打印出来

show_struct_align

    :将使用的字节对齐方式打印出来
// gcc 2_calc_size.c -o 2_calc_size
#include <stdio.h>
#include <stddef.h>

typedefstruct{
    int   m1;
    float m2;
    char  m3[2];
}DataA_t;

typedefstruct{
    char   n1[3];
    double n2;
}DataB_t;

typedefstruct{
    char x1;
    char x2[2];
}DataC_t;

typedefstruct{
    DataA_t data_a;
    DataB_t data_b;
    DataC_t data_c;
    char    data_d[1];
}DataAll_t;

typedefstruct{
    char * name;
    size_t offset;
    size_t size;
}StructMemberInfo_t;

void show_struct_member_size_info(DataAll_t *data)
{
    StructMemberInfo_t info[] = {
        {"data_a", offsetof(DataAll_t, data_a), sizeof(data->data_a)},
        {"data_b", offsetof(DataAll_t, data_b), sizeof(data->data_b)},
        {"data_c", offsetof(DataAll_t, data_c), sizeof(data->data_c)},
        {"data_d", offsetof(DataAll_t, data_d), sizeof(data->data_d)},
    };
    
    int num = sizeof(info) / sizeof(info[0]);
    size_t sum_size = 0;
    
    size_t total_size = sizeof(DataAll_t);
    printf("datap:%pt total_size:%zun", (void *)data, total_size);
    
    for (int i = 0; i < num; i++)
    {
        void *ptr = (void *)data + info[i].offset;
        sum_size += info[i].size;
        printf("[%d] p:%pt name:%st offset:%zut size:%zut sum_size:%zun",
            i, ptr, info[i].name, info[i].offset, info[i].size, sum_size);
    }
    
    if (sum_size == total_size)
    {
        printf("struct data member size check ok, sum_size:%zu, total_size:%zun", sum_size, total_size);
    }
    else
    {
        printf("struct data member size check fail, sum_size:%zu, total_size:%zun", sum_size, total_size);
    }
}

void show_struct_member_addr(DataAll_t *data)
{
    printf("====== data:%pn", (void *)data);
    printf("------ data_a:%pn", (void *)&data->data_a);
    printf("data_a,m1:%pn", (void *)&data->data_a.m1);
    printf("data_a,m2:%pn", (void *)&data->data_a.m2);
    printf("data_a,m3:%pn", (void *)&data->data_a.m3);
    
    printf("------ data_b:%pn", (void *)&data->data_b);
    printf("data_b,n1:%pn", (void *)&data->data_b.n1);
    printf("data_b,n2:%pn", (void *)&data->data_b.n2);
    
    printf("------ data_c:%pn", (void *)&data->data_c);
    printf("data_c,x1:%pn", (void *)&data->data_c.x1);
    printf("data_c,x2:%pn", (void *)&data->data_c.x2);
    
    printf("------ data_d:%pn", (void *)&data->data_d);
}

void show_struct_align(DataAll_t *data)
{
    printf("====== data align:%zun", __alignof(DataAll_t));
    printf("------ data_a align:%zun", __alignof(data->data_a));
    printf("------ data_b align:%zun", __alignof(data->data_b));
    printf("------ data_b align:%zun", __alignof(data->data_c));
    printf("------ data_d align:%zun", __alignof(data->data_d));
}

int main()
{
    DataAll_t data_all;
    
    show_struct_member_addr(&data_all);
    
    printf("n");
    show_struct_align(&data_all);
    
    printf("n");
    show_struct_member_size_info(&data_all);
    
    return0;
}

运行结果如下:

可以看到,最外层结构体是8字节对齐,内部的子结构体,存在多种对齐:

    DataA_t:4字节对齐,int和float的大小是4DataB_t:8字节对齐,double的大小是8DataC_t:1字节对齐,char的大小是1

再根据各个成员的地址,可以画出如下示例图,看出字节对齐对应的内存填充(padding)的位置:

    例如4字节对齐的data_a,从前到后,如果存在连续的成员组合起来的大小不是4的倍数,则进行内存填充,确保组合后是4的倍数,data_a的最后一个成员,只剩2了,所以要在末尾填充2,所以sizeof(data_a)的12对于8字节对齐的data_b,第一个成员是3,和后面的成员组合在一起,也不是8的倍数,所以需要先在第1个成员后添加5对于8字节对齐data_all,显示需要在data_a后补4个,凑够了8,data_b是8的倍数,不用管,后面的data_c和data_d,加在一起还不够8,所以最后再补上4

2.3 增加对齐的计算

经过上述的分析,可以对show_struct_member_size_info增加字节对齐中内存填充大小的计算,然后再验证累加的大小(包括字节填充)与整体的大小是否一致,代码如下:

// gcc 3_calc_size.c -o 3_calc_size
#include <stdio.h>
#include <stddef.h>

typedefstruct{
    int   m1;
    float m2;
    char  m3[2];
}DataA_t;

typedefstruct{
    char   n1[3];
    double n2;
}DataB_t;

typedefstruct{
    char x1;
    char x2[2];
}DataC_t;

typedefstruct{
    DataA_t data_a;
    DataB_t data_b;
    DataC_t data_c;
    char    data_d[1];
}DataAll_t;

typedefstruct{
    char * name;
    size_t offset;
    size_t size;
}StructMemberInfo_t;

void show_struct_member_size_info(DataAll_t *data)
{
    StructMemberInfo_t info[] = {
        {"data_a", offsetof(DataAll_t, data_a), sizeof(data->data_a)},
        {"data_b", offsetof(DataAll_t, data_b), sizeof(data->data_b)},
        {"data_c", offsetof(DataAll_t, data_c), sizeof(data->data_c)},
        {"data_d", offsetof(DataAll_t, data_d), sizeof(data->data_d)},
    };
    
    int num = sizeof(info) / sizeof(info[0]);
    size_t sum_size = 0;
    
    size_t total_size = sizeof(DataAll_t);
    printf("datap:%pt total_size:%zun", (void *)data, total_size);
    
    for (int i = 0; i < num; i++)
    {
        void *ptr = (void *)data + info[i].offset;
          
        // sum_size加上字节对齐的值
        if (i > 0)
        {
            size_t padding = info[i].offset - sum_size;
            if (padding > 0)
            {
                printf("[%d] name:%s need add padding:%zu(info[%d].offset:%zu, sum_size:%zu)n", 
                    i-1, info[i-1].name, padding, i, info[i].offset, sum_size);
                sum_size += padding;
            }
        }
        sum_size += info[i].size;
        
        printf("[%d] p:%pt name:%st offset:%zut size:%zut sum_size:%zun",
            i, ptr, info[i].name, info[i].offset, info[i].size, sum_size);
    }
    
    // 最后加上字节对齐的值
    {
        size_t align = __alignof(data);

        if (sum_size % align != 0)
        {
            size_t padding = align - (sum_size % align);
            sum_size += padding;
            printf("in the end, need add padding:%zun", padding);
        }
    }
    
    if (sum_size == total_size)
    {
        printf("struct data member size check ok, sum_size:%zu, total_size:%zun", sum_size, total_size);
    }
    else
    {
        printf("struct data member size check fail, sum_size:%zu, total_size:%zun", sum_size, total_size);
    }
}

void show_struct_member_addr(DataAll_t *data)
{
    printf("====== data:%pn", (void *)data);
    printf("------ data_a:%pn", (void *)&data->data_a);
    printf("data_a,m1:%pn", (void *)&data->data_a.m1);
    printf("data_a,m2:%pn", (void *)&data->data_a.m2);
    printf("data_a,m3:%pn", (void *)&data->data_a.m3);
    
    printf("------ data_b:%pn", (void *)&data->data_b);
    printf("data_b,n1:%pn", (void *)&data->data_b.n1);
    printf("data_b,n2:%pn", (void *)&data->data_b.n2);
    
    printf("------ data_c:%pn", (void *)&data->data_c);
    printf("data_c,x1:%pn", (void *)&data->data_c.x1);
    printf("data_c,x2:%pn", (void *)&data->data_c.x2);
    
    printf("------ data_d:%pn", (void *)&data->data_d);
}

void show_struct_align(DataAll_t *data)
{
    printf("====== data align:%zun", __alignof(DataAll_t));
    printf("------ data_a align:%zun", __alignof(data->data_a));
    printf("------ data_b align:%zun", __alignof(data->data_b));
    printf("------ data_b align:%zun", __alignof(data->data_c));
    printf("------ data_d align:%zun", __alignof(data->data_d));
}

int main()
{
    DataAll_t data_all;
    
    show_struct_member_addr(&data_all);
    
    printf("n");
    show_struct_align(&data_all);
    
    printf("n");
    show_struct_member_size_info(&data_all);
       
    return0;
}

运行结如下,可以看到最终计算一致了:

3 总结

本篇探究了C/C++中结构体的字节对齐问题,通过一个简单的示例,展示字节对齐的实际现象,以及通过打印出地址,说明结构体成员是如何存储的。

 

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录