title: C语言结构体大小分析
author: saopigqwq233
date: 2022-04-05

C语言结构体大小分析

一,基本类型

C语言自带的数据类型大小如下

数据类型 大小(字节)
char 1
short 2
int 4
long 4或8
float 4
double 8
long double 16

二,自定义类型---struct

C语言除了以上这些基本类型,还支持用户自己定义数据类型

类似于一下形式:

struct Student{
    char name[10];//学生姓名
    int age;//学生年龄
};

这里自定义了一个struct Student 类型的数据类型,包含有字符数组整形两种内容。

三,自定义结构体大小分析

1.错误示范

如果直接按照结构体内的成员类型相加,那么如上struct Student类型的大小应该是

10+4=14

似乎没什么问题,但是,当我们用以下代码做测试时,却发现出了问题

#include"stdio.h"
#include"stdlib.h"
struct Student{
    char name[10];//学生姓名
    int age;//学生年龄
};
int main()
{
    printf("%dn",sizeof(struct Student));//测试struct Student大小
    system("pause");
    return 0;
}

运行结果

可以发现,结构体大小并非结构体成员大小简单相加

为什么会出现这样的结果?这是因为C语言中存在一种行为叫结构体内存对齐。

2.内存对齐

结构体成员在内存存放时,编译器会在结构体成员之间添加填充字节,以保证结构体成员的对齐要求。

2.1对齐规则

1)结构体第一个成员永远放在0偏移处

在C语言中,结构体第一个成员的地址和整个结构体的初始地址是相同的,也就是说,结构体的第一个成员始终位于结构体的初始地址处。

可以用以下代码证明:

#include"stdio.h"
#include"stdlib.h"
struct student {
    char name[20];
    int age;
    float score;
};
int main()
{
    struct student stu;
    printf("%pn%pn",&(stu),&(stu.name));
    system("pause");
    return 0;
}

运行结果如下(不同设备上运行的数值可能不相同,但是一台设备上两行的数据相同):

可以看到,结构体变量的地址和结构体变量第一个成员的地址是相同的。

2)从第二个成员开始,以后的每个成员的地址距离都要对齐到某个对齐数的整数倍处,这个对齐数是:(成员自身大小和默认对齐数)的较小值

这句话是什么意思呢,让我们先看一个例子:

#include"stdio.h"
struct S
{
    char a;
    int b;
    char c;
    long long d;
}s;//创建结构体变量s
int main()
{
    printf("结构体大小:%dn",sizeof(struct S));
    printf("各个成员的地址:n");
    printf("%p char an",&(s.a));
    printf("%p int bn",&(s.b));
    printf("%p char cn",&(s.c));
    printf("%p long long d",&(s.d));
    return 0;
}

运行结果如下:

需要注意的是,%p是以16进制的格式进行输出,最后一个long long 型数据d的大小为8字节,则其结束地址应该是7d057

因此,结构体大小:7d057(16)-7d039(16)=18(16)=24(10)

接下来我们将以excel表格代表内存空间,分析每个成员在内存的分布

其中,D列的0到23代表每个地址距离起始地址的偏移量

@1 首先是 char a,已知结构体第一个成员永远放在0偏移处,且char 只占1字节,那么a在内存的分布暂时是这样的

@2 接下来是int b,第二个成员要对齐到对齐数的整数倍,也就是说,它的起始地址的偏移量必须是对齐数的整数倍。

对齐数:1)数据类型自身的对齐数:char型数据自身对齐值为1字节,short型数据为2字节,int/float型为4字节,double型为8字节。

2)默认对齐数:VS2013默认对齐数为8,或#pragma pack (value)时的指定对齐值value。

3)数据成员、结构体的有效对齐数:自身对齐值和指定对齐值中较小者,即有效对齐值=min{自身对齐值,当前指定的pack值}。

*需要注意的是gcc无默认对齐数

由于我使用的是gcc编译器,则int b的对齐数是其自身对齐值4,如果使用的是Visual Studio这个IDE,那么其对齐数为min(4,8)=4

因此,编译器将会在成员char a后再填补3个字节,使int b对齐,内存分布如下:

@3 接下来是char c,其对齐数是min(1,8)=1,大小是1字节,那么,直接接在偏移量为8的地方

@4 最后一个成员是long long d,对齐数是8,大小8字节,则需要对齐到8*2=16这个偏移量的地址,并在char c占用内存后填补7个字节,内存分布如下:

3)结构体大小是所有成员对齐数中最大对齐数的整数倍

当最后一个结构体成员存放后,如果结构体大小不是所有成员的对齐数中最大对齐数的整数倍,那么会在结构体最后的成员后补字节。

#include"stdio.h"
struct S{
    int a;
    char c;
}s;
int main()
{
    printf("%d struct Sn",sizeof(struct S));
    printf("%p int an",&(s.a));
    printf("%p char cn",&(s.c));
    return 0;
}
return 0;
}

接下来我们分析结构体成员是如何分布在内存中的

@1 首先是int a,对齐数为4,大小是4个字节,由于是第一个成员,直接放在0偏移处

@2 其次是char c,对齐数为1,大小是一个字节,则可以存放在偏移量为4的地方

@3 我们发现,如果结构体到这里就分配结束,那么结构体大小应该为5,但是实际情况却是结构体大小为8。实际上结构体也要进行内存对齐。

此结构体中int a和char c的对齐数分别为4和1,结构体对齐数是成员对齐数中的最大对齐数,则此结构体对齐数大小MAX(4,1)=4,那么,就需要在char c后填补字节到结构体大小为8.

4)嵌套结构体中子结构体对齐到子结构体自己成员的最大对齐数的整数倍

@1 offsetof(type, member-designator) 求偏移量宏

此库宏需要包含头文件“stddef.h”,会生成一个类型为size_t的整形数,代表该成员在内存中距离起始地址的偏移量。

实例可参考:C 库宏 – offsetof() | 菜鸟教程 (runoob.com)

话不多说,上代码:

#include"stdio.h"
#include"stddef.h"
struct stu{
    int name;
    double grades;
};
struct team{
    char name[6];
    struct stu Students;
    int num_stu;
};
int main()
{
    struct team Team;
    printf("%d struct stun",sizeof(struct stu));
    printf("%d struct teamn",sizeof(struct team));
    printf("%p char name[10]n"
    "%p struct stu Studentsn"
    "%p int num_stun",(Team.name),&(Team.Students),&(Team.num_stu));
    printf("%d struct stu Studentsn%d int num_stun",offsetof(struct team,Students),offsetof(struct team,num_stu));
    return 0;
}

运行结果如下:

@2 根据上面三个规律,可以得出struct stu的大小是16,接下来我们看看struct team的成员是怎么分布的

@3 首先是char name[6],第一个成员直接占用6字节,我知道你们都懂

内容来源于网络如有侵权请私信删除
你还没有登录,请先登录注册
  • 还没有人评论,欢迎说说您的想法!