LV01-18-C语言-结构体、联合体占用空间

本文主要是C语言基础——结构体、联合体占用空间计算的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

点击查看使用工具及版本
Windows windows11
Ubuntu Ubuntu16.04的64位版本
VMware® Workstation 16 Pro 16.2.3 build-19376536
SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日
开发板 正点原子 i.MX6ULL Linux阿尔法开发板
uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03)
linux内核 linux-4.15(NXP官方提供)
STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
点击查看相关文件下载
--- ---

一、结构体

1.结构体大小计算规则

1.1成员的数据类型都相同

结构体占用空间=成员个数×成员数据类型的长度。

1.2成员的数据类型不相同

类型不相同的时候,按结构体默认的字节对齐一般满足三个准则:

  • (1)结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
  • (2) 结构体每个成员相对于结构体首地址的偏移量( offset )都是成员大小的整数倍( 0 被认为是任何数的整数倍),如有需要编译器会在成员之间加上填充字节( internal adding );
  • (3)当没有定义 #pragma pack(value) 这种指定 value 字节进行对齐时,整体的大小在满足为最大数据类型所占字节的倍数下要达到所占内存最小(最大数据类型所占字节数的最小整数倍)。当定义了 #pragma pack(value) ,以 value 字节进行对齐时,它整体的大小必须为 value 的最小整数倍。如有需要编译器会在最末一个成员之后加上填充字节( trailing padding )。

【方法】后来发现,其实可以按照下边的方法来计算:

(1)首先找出成员变量中最大数据类型占用的字节数 Lmax ;

(2)所有成员分析时都按照 Lmax 为准,先画一个含有 Lmax 个字节空位,然后各个成员按照大小开始往上填;

(3)成员相对于首地址偏移量需要是成员类型大小的整数倍,不是整数倍的可以在前边补空字节,若这个成员大小加上偏移量超过 Lmax ,则移到下一行(再画一个含有 Lmax 字节的空位);

点击查看例子
1
2
3
4
5
6
7
struct
{
char a;
char b;
int c;
double d;
} S1;
1

1.3特殊的结构体

对于一个没有任何成员的结构体,在 C 中占用空间为 0 ,而在 C++ 中它占用的空间为 1 字节,这是因为 struct 在 C++ 中相当于类。

2.实例分析

2.1相同成员结构体

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <stdio.h>
struct TEST1
{
int a;
int b;
int c[2];
};
struct TEST2
{
char a;
char b;
char c[2];
};

int main(int argc, char *argv[])
{
struct TEST1 A;
struct TEST2 B;
printf("sizeof(A)=%ld,sizeof(A.a)=%ld,sizeof(A.b)=%ld,sizeof(A.c)=%ld\n", sizeof(A), sizeof(A.a), sizeof(A.b), sizeof(A.c));
printf("&A =%p\n&A.a =%p\n&A.b =%p\n&A.c[0]=%p\n&A.c[1]=%p\n", &A, &A.a, &A.b, &A.c[0], &A.c[1]);

printf("sizeof(B)=%ld,sizeof(B.a)=%ld,sizeof(B.b)=%ld,sizeof(B.c)=%ld\n", sizeof(B), sizeof(B.a), sizeof(B.b), sizeof(B.c));
printf("&B =%p\n&B.a =%p\n&B.b =%p\n&B.c[0]=%p\n&B.c[1]=%p\n", &B, &B.a, &B.b, &B.c[0], &B.c[1]);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
5
6
7
8
9
10
11
12
sizeof(A)=16,sizeof(A.a)=4,sizeof(A.b)=4,sizeof(A.c)=8
&A =0x7ffe9c0871e0
&A.a =0x7ffe9c0871e0
&A.b =0x7ffe9c0871e4
&A.c[0]=0x7ffe9c0871e8
&A.c[1]=0x7ffe9c0871ec
sizeof(B)=4,sizeof(B.a)=1,sizeof(B.b)=1,sizeof(B.c)=2
&B =0x7ffe9c0871dc
&B.a =0x7ffe9c0871dc
&B.b =0x7ffe9c0871dd
&B.c[0]=0x7ffe9c0871de
&B.c[1]=0x7ffe9c0871df

2.2特殊结构体

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
struct TEST1
{
};

int main(int argc, char *argv[])
{
struct TEST1 A;
printf("sizeof(A)=%ld\n", sizeof(A));
printf("&A=%p\n", &A);

return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
sizeof(A)=0
&A=0x7fff18b05457

2.3成员不同的结构体

2.3.1实例1

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
struct
{
char a;
short b;
char c;
} S1;

struct
{
char a;
char b;
short c;
} S2;

int main(int argc, char *argv[])
{
printf("sizeof(S1)=%ld,sizeof(S1.a)=%ld,sizeof(S1.b)=%ld,sizeof(S1.c)=%ld\n",
sizeof(S1), sizeof(S1.a), sizeof(S1.b), sizeof(S1.c));
printf("&S1=%p \n&S1.a=%p \n&S1.b=%p \n&S1.c=%p \n",
&S1, &S1.a, &S1.b, &S1.c);

printf("sizeof(S2)=%ld,sizeof(S2.a)=%ld,sizeof(S2.b)=%ld,sizeof(S2.c)=%ld\n",
sizeof(S2), sizeof(S2.a), sizeof(S2.b), sizeof(S2.c));
printf("&S2=%p \n&S2.a=%p \n&S2.b=%p \n&S2.c=%p \n",
&S2, &S2.a, &S2.b, &S2.c);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
5
6
7
8
9
10
sizeof(S1)=6,sizeof(S1.a)=1,sizeof(S1.b)=2,sizeof(S1.c)=1
&S1=0x5576e7e5b012
&S1.a=0x5576e7e5b012
&S1.b=0x5576e7e5b014
&S1.c=0x5576e7e5b016
sizeof(S2)=4,sizeof(S2.a)=1,sizeof(S2.b)=1,sizeof(S2.c)=2
&S2=0x5576e7e5b018
&S2.a=0x5576e7e5b018
&S2.b=0x5576e7e5b019
&S2.c=0x5576e7e5b01a

我们来分析一下这两个结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
struct
{
char a;
short b;
char c;
} S1;

struct
{
char a;
char b;
short c;
} S2;
  • S1 结构体

(1)成员 a : char 类型,相对于首地址偏移量为 0 ,占据 1 字节空间;

(2)成员 b : short 类型,此时前边有一个 char 类型,这就意味着该成员相对于首地址的偏移量为 1 ,而 short 类型占据 2 个字节,所以前边需要补一个字节,这就导致该成员的偏移量变为 2 ,然后再占据 2 个字节的空间;

(3)成员 c : char 类型,相对于首地址的偏移量为 2+2=4 ,是成员大小的整数倍,并占据 1 字节空间。

所以目前一共就是 2+2+1=5 字节,但是,根据第三个原则,结构体的总大小需要是结构体成员中最大数据类型所占字节数的整数倍,也就是 2 的整数倍,所以需要在成员 c 后边补一个字节,这样整个结构体就占据了 6 个字节,即:

image-20220723194454122
  • S2 结构体

(1)成员 a : char 类型,相对于首地址偏移量为 0 ,占据 1 字节空间;

(2)成员 b : char 类型,此时前边有一个 char 类型,这就意味着该成员相对于首地址的偏移量为 1 ,而 char 类型占据 1 个字节,偏移量是成员大小的整数倍,不需要补字节;

(3)成员 c : short 类型,相对于首地址的偏移量为 1+1=2 ,是成员大小的整数倍,并占据 2 字节空间。

所以一共就是 1+1+2=4 字节,即:

image-20220723195127642

2.3.2实例2

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
struct
{
char a;
char b;
double c;
short d;
} S1;


int main(int argc, char *argv[])
{
printf("sizeof(S1)=%ld,sizeof(S1.a)=%ld,sizeof(S1.b)=%ld,sizeof(S1.c)=%ld, sizeof(S1.d)=%ld\n",
sizeof(S1), sizeof(S1.a), sizeof(S1.b), sizeof(S1.c), sizeof(S1.d));
printf("&S1=%p \n&S1.a=%p \n&S1.b=%p \n&S1.c=%p \n&S1.d=%p\n",
&S1, &S1.a, &S1.b, &S1.c, &S1.d);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
5
6
sizeof(S1)=24,sizeof(S1.a)=1,sizeof(S1.b)=1,sizeof(S1.c)=8, sizeof(S1.d)=2
&S1=0x55da00a7c020
&S1.a=0x55da00a7c020
&S1.b=0x55da00a7c021
&S1.c=0x55da00a7c028
&S1.d=0x55da00a7c030

我们来分析一下这个结构体:

1
2
3
4
5
6
7
struct
{
char a; /* # */
char b; /* # */
double c; /* ######## */
short d; /* ##000000 */
} S1;
  • S1 结构体

(1)成员 a : char 类型,相对于首地址偏移量为 0 ,占据 1 字节空间;

(2)成员 b : char 类型,此时前边有一个 char 类型,这就意味着该成员相对于首地址的偏移量为 1 ,是成员大小的整数倍;

(3)成员 c : double 类型,相对于首地址的偏移量为 1+1=2 ,不是成员大小的整数倍,它的大小为 8 字节,所以前边补 6 个字节,那么这个成员的实际偏移量是 8 了,然后再占据 8 字节空间。

(4)成员 d : short 类型,相对于首地址偏移量为 8+8=16 ,是成员大小的整数倍。

所以目前一共就是 1+1+6+8+2=18 字节,但是根据第三条规则,结构体的总大小需要是结构体成员中最大数据类型所占字节数的整数倍,也就是 8 的整数倍,所以最少需要在成员 d 后边补 6 个字节,这样整个结构体就占据了 24 个字节,即

image-20220723204022488

2.3.3实例3

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
struct
{
char a;
int b;
char c[5];
} S1;

int main(int argc, char *argv[])
{
printf("sizeof(S1)=%ld,sizeof(S1.a)=%ld,sizeof(S1.b)=%ld,sizeof(S1.c)=%ld\n",
sizeof(S1), sizeof(S1.a), sizeof(S1.b), sizeof(S1.c));
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
5
sizeof(S1)=16,sizeof(S1.a)=1,sizeof(S1.b)=4,sizeof(S1.c)=5
&S1=0x562315725020
&S1.a=0x562315725020
&S1.b=0x562315725024
&S1.c=0x562315725028

我们来分析一下这个结构体:

1
2
3
4
5
6
struct
{
char a;
int b;
char c[5];
} S1;
  • S1 结构体

(1)成员 a : char 类型,相对于首地址偏移量为 0 ,占据 1 字节空间;

(2)成员 b : int 类型,此时前边有一个 char 类型,这就意味着该成员相对于首地址的偏移量为 1 ,这就不是成员大小的整数倍了,前边补 3 个字节, b 的实际偏移量变成 4 然后再占据 4 字节空间;

(3)成员 c : char 类型数组,包含 5 个成员,成员大小为 5 字节,相对于首地址的偏移量为 4+4=8 ,偏移量是数据类型的整数倍。

所以目前一共就是 1+3+4+5=13 字节,但是根据第三条规则,结构体的总大小需要是结构体成员中最大数据类型所占字节数的整数倍,也就是 4 的整数倍,所以最少需要在成员 c 后边补 3 个字节,这样整个结构体就占据了 16 个字节。

2.3.4实例4

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
struct
{
char a;
int b;
struct
{
char c;
int d;
double e;
} s;
float f;
} S1;

int main(int argc, char *argv[])
{
printf("sizeof(S1) =%2ld, addr=%p\n",sizeof(S1), &S1);
printf("sizeof(S1.a) =%2ld, addr=%p\n",sizeof(S1.a), &S1.a);
printf("sizeof(S1.b) =%2ld, addr=%p\n",sizeof(S1.b), &S1.b);
printf("sizeof(S1.s) =%2ld, addr=%p\n",sizeof(S1.s), &S1.s);
printf("sizeof(S1.s.c)=%2ld, addr=%p\n",sizeof(S1.s.c), &S1.s.c);
printf("sizeof(S1.s.d)=%2ld, addr=%p\n",sizeof(S1.s.d), &S1.s.d);
printf("sizeof(S1.s.e)=%2ld, addr=%p\n",sizeof(S1.s.e), &S1.s.e);
printf("sizeof(S1.f) =%2ld, addr=%p\n",sizeof(S1.f), &S1.f);

return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
5
6
7
8
sizeof(S1)    =32, addr=0x556695193040
sizeof(S1.a) = 1, addr=0x556695193040
sizeof(S1.b) = 4, addr=0x556695193044
sizeof(S1.s) =16, addr=0x556695193048
sizeof(S1.s.c)= 1, addr=0x556695193048
sizeof(S1.s.d)= 4, addr=0x55669519304c
sizeof(S1.s.e)= 8, addr=0x556695193050
sizeof(S1.f) = 4, addr=0x556695193058

我们来分析一下这个结构体:

1
2
3
4
5
6
7
8
9
10
11
12
struct
{
char a;
int b;
struct
{
char c;
int d;
double e;
} s;
float f;
} S1;
  • S1 结构体
image-20220724101808691

2.3.5实例5

该实例使用了 attribute 机制,设置了 packed 属性。

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
struct __attribute__((__packed__))
{
char a;
int b;
struct
{
char c;
int d;
double e;
} s;
float f;
} S1;

int main(int argc, char *argv[])
{
printf("sizeof(S1) =%2ld, addr=%p\n",sizeof(S1), &S1);
printf("sizeof(S1.a) =%2ld, addr=%p\n",sizeof(S1.a), &S1.a);
printf("sizeof(S1.b) =%2ld, addr=%p\n",sizeof(S1.b), &S1.b);
printf("sizeof(S1.s) =%2ld, addr=%p\n",sizeof(S1.s), &S1.s);
printf("sizeof(S1.s.c)=%2ld, addr=%p\n",sizeof(S1.s.c), &S1.s.c);
printf("sizeof(S1.s.d)=%2ld, addr=%p\n",sizeof(S1.s.d), &S1.s.d);
printf("sizeof(S1.s.e)=%2ld, addr=%p\n",sizeof(S1.s.e), &S1.s.e);
printf("sizeof(S1.f) =%2ld, addr=%p\n",sizeof(S1.f), &S1.f);

return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
5
6
7
8
sizeof(S1)    =25, addr=0x556da3484020
sizeof(S1.a) = 1, addr=0x556da3484020
sizeof(S1.b) = 4, addr=0x556da3484021
sizeof(S1.s) =16, addr=0x556da3484025
sizeof(S1.s.c)= 1, addr=0x556da3484025
sizeof(S1.s.d)= 4, addr=0x556da3484029
sizeof(S1.s.e)= 8, addr=0x556da348402d
sizeof(S1.f) = 4, addr=0x556da3484035

这样的话,结构体的大小就直接是各个变量大小之和了。

二、联合体

1.联合体大小计算规则

1.1成员的数据类型都相同

联合体占用空间大小=任一成员数据类型大小。

1.2成员的数据类型不相同

类型不相同的时候,分两种情况:

  • 没有定义 #pragma pack(value)

联合体中最大成员所占内存的大小且必须为最大数据类型所占字节的最小倍数

  • 定义了 #pragma pack(value)

联合体中最大成员所占字节数必须为 value 的最小倍数。

1.3特殊的联合体

对于一个没有任何成员的联合体,在 C 中占用空间为 0 。

2.实例分析

2.1成员相同的联合体

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
union
{
char a;
char b;
} S1;

int main(int argc, char *argv[])
{
printf("sizeof(S1) =%2ld, addr=%p\n",sizeof(S1), &S1);
printf("sizeof(S1.a) =%2ld, addr=%p\n",sizeof(S1.a), &S1.a);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
sizeof(S1)    = 1, addr=0x564129368011
sizeof(S1.a) = 1, addr=0x564129368011

2.2特殊联合体

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
union
{
} S1;

int main(int argc, char *argv[])
{
printf("sizeof(S1) =%2ld, addr=%p\n",sizeof(S1), &S1);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
sizeof(S1)    = 0, addr=0x564c0595f011

2.3成员不同的联合体

2.3.1实例1

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
union
{
char a;
double b;
char c[9];
} S1;

int main(int argc, char *argv[])
{
printf("sizeof(S1) =%2ld, addr=%p\n",sizeof(S1), &S1);
printf("sizeof(S1.a) =%2ld, addr=%p\n",sizeof(S1.a), &S1.a);
printf("sizeof(S1.b) =%2ld, addr=%p\n",sizeof(S1.b), &S1.b);
printf("sizeof(S1.c) =%2ld, addr=%p\n",sizeof(S1.c), &S1.c);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
sizeof(S1)    =16, addr=0x556b80767020
sizeof(S1.a) = 1, addr=0x556b80767020
sizeof(S1.b) = 8, addr=0x556b80767020
sizeof(S1.c) = 9, addr=0x556b80767020

2.3.1实例2

该实例使用了 attribute 机制,设置了 packed 属性。

点击查看实例
test.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
union __attribute__((__packed__))
{
char a;
double b;
char c[9];
} S1;

int main(int argc, char *argv[])
{
printf("sizeof(S1) =%2ld, addr=%p\n",sizeof(S1), &S1);
printf("sizeof(S1.a) =%2ld, addr=%p\n",sizeof(S1.a), &S1.a);
printf("sizeof(S1.b) =%2ld, addr=%p\n",sizeof(S1.b), &S1.b);
printf("sizeof(S1.c) =%2ld, addr=%p\n",sizeof(S1.c), &S1.c);
return 0;
}

在终端执行以下命令编译程序:

1
2
gcc test.c -Wall # 生成可执行文件 a.out 
./a.out # 执行可执行程序

然后,终端会有以下信息显示:

1
2
3
4
sizeof(S1)    = 9, addr=0x5611cb03e018
sizeof(S1.a) = 1, addr=0x5611cb03e018
sizeof(S1.b) = 8, addr=0x5611cb03e018
sizeof(S1.c) = 9, addr=0x5611cb03e018