本文章中,倘若 代码的实现概念剖析 没有声明是在 64位 操作系统中,

则一律按 32位 操作系统来定

系统内存中数据的存储

# 原码,补码,反码

在计算机系统内存中,数据存储形式为`二进制``补码`
当打印时需转换为原码。其中,`有符号类型中的正整数类型``无符号类型`
原码、反码和补码都相同,而负数的原码、反码和补码都不相同。(`规定`)
`原码`可通过将`十进制`转为`二进制`取得,
`反码`可通过将原码`符号位`保持`不变`,其他位`取反`取得,
`补码`可通过将`反码加一`取得。
-14:
`原码`:10000000000000000000000000001110
`反码`:11111111111111111111111111110001
`补码`:11111111111111111111111111110010

# 整型提升

int main()
{
    char a = -1;
    signed char b = -1;
    unsigned char c = -1;
    
    printf("a = %d, b = %d, c = %d", a, b, c);
    return 0;
}
// 过程剖析
/*
1.`-1` 是整型形式,不管接收 `-1` 的变量是什么类型,
在计算机内存中其存储形式为 `4` 个字节 (即 `32` 个比特位) 的 `补码`。
--------------------------------------
`-1` 在内存中的存储形式
补码:11111111111111111111111111111111
--------------------------------------
2.`char` 类型只会获取当前变量存储单元中的 `1` 个字节即 `8` 个比特位。
补码
---------------------
char     a: 11111111
signed   b: 11111111
unsigned c: 11111111
---------------------
当以 `% d` 的形式打印时,打印的是变量 `原码` 的十进制,
倘若需打印的变量是 `有符号` 的,则会发生整型提升,
所以此处打印 `a` 的值为 `-1`。
-----------------------------------------
char a
整型提升:有符号变量以 `符号位` 进行扩充 (补码扩充)
补码:11111111111111111111111111111111
反码:11111111111111111111111111111110
原码:10000000000000000000000000000001
-----------------------------------------
----------------------------------------------------
3.(signed char) b 与 (char) a 都是同样的有符号变量,因此打印 `b` 的值也为 `-1`。
----------------------------------------------------
4.`变量 c` 是 `无符号` 变量,内存中原码、反码和补码都相同,
并且最高位不再充当 `符号位` 而是参与计算的实数,
因此整型提升时按规定以数字 `0` 进行扩充,最后打印 `c` 的值为 `255`。
---------------------------------------
unsigned char c
补码:00000000000000000000000011111111
反码:00000000000000000000000011111111
原码:00000000000000000000000011111111
---------------------------------------
*/
int main()
{
    char a = -128;
    char b = 128;
    
    printf("a = %u\nb = %u", a, b);
    return 0;
}
// 过程剖析
/*
1.char 为有符号字符类型,在内存中存储 `1` 个字节即 `8` 个比特位,
并且其二进制表示中的最高位为符号位,
当 char 类型变量所存储的值为 `127` 时,倘若再与整型数字 `1` 相加,
这会使符号位变为 `1`,此时的值按照规定应该是 `-128`。
------------------
 127 补码:01111111
-128 补码:10000000
-----------------
2. 因此,有符号 char 类型值的范围是 `-128 ~ 127`.
闭环
-----------------------------
系统内存中数值存储 (补码) 范围为
  ~
  -1:11111111
   0:00000000
      .......~
 127:01111111
-128:10000000
-127:10000001
	  .......~
  -1:11111111
   0:00000000
  ~
-----------------------------
3. 假如按照 `% u` 的形式打印,打印的是 `无符号` 变量 `原码` 的十进制,
此时,有两种情况
----------------------------------------
倘若传入的是 `有符号` 变量的负数值,
则需以 `符号位` 进行补码的扩充来实现 `整型提升`;
倘若传入的是 `无符号` 变量的负数值,
则需以数字 `0` 进行补码的扩充来实现 `整型提升`;
----------------------------------------
此外,由于 `% u` 的影响,
此时该已经完成了 `整型提升` 的负数 `值` 的原码、反码和补码都相同,
因此,按 `% u` 的形式打印时,只需 `变量` 对应值的 `补码` 即可。
4. 此时变量 `a` 的值带有符号,因此,
需以 `符号位` 进行补码的扩充来实现 `整型提升`,
最后再按 `原码` 的形式将值打印出来。
-128 以 `% u` 格式打印时的内存存储
---------------------------------------
补码:111111111111111111111111`10000000`
反码:111111111111111111111111`10000000`
原码:111111111111111111111111`10000000`
---------------------------------------
5. 而对于值为 `128` 的 `b`,此时内存中所获取的 `补码` 为
-----------------------------------------------
补码:10000000
等同:01111111 + 00000001
-----------------------------------------------
即 `127 + 1`,相当于赋值 `-128`,
为此当以 % u 形式打印时,也需以 `符号位` 进行补码的扩充来实现 `整型提升`,
最后再按 `原码` 的形式将值打印出来。
---------------------------------------
补码:11111111111111111111111110000000
反码:11111111111111111111111110000000
原码:11111111111111111111111110000000
---------------------------------------
*/

# 数据储存闭环 (整型)

整型家族(char类型存储值为其对应的`ASCII码`值)
char
signed char
unsigned char
short
signed short
unsigned short
int 
signed int
unsigned int
long
long long
...............
`char`类型在内存存储中只存放`1`个字节即`8`个比特位,
倘若`char`类型变量所赋予的`值``整型`时,
`值`在内存中所存储的字节仍为`8`个即`32`个比特位,
但由于char类型的`定义`,因此只会获取此`值`内存中的`1`个字节。
signed char 与 char 都是`有符号`类型,其数值范围为
`-128 ~ 127`
-------------------------------------------------------------
当在正数范围内时,系统内存中二进制存储范围(`红色区域`)为
  00000000000000000000000000000000`00000000`   0
~ 
  00000000000000000000000000000000`01111111`   127
当在负数范围内时,系统内存中二进制存储范围(`红色区域`)为
  11111111111111111111111111111111`10000000`    -128(规定值)
~ 
  11111111111111111111111111111111`11111111`    -1
`127 + 1`
  00000000000000000000000000000000`01111111`   127
+ 
  00000000000000000000000000000000`00000001`   1
-> 
  11111111111111111111111111111111`10000000`  -128
 
`-1 + 1`
  11111111111111111111111111111111`11111111`  -1
+ 
  00000000000000000000000000000000`00000001`   1
—> 
  11111111111111111111111111111111`00000000`   0
-------------------------------------------------------------
unsigned char 为`无符号`类型,
其二进制的`最高位`不再充当`符号位`而是参与计算的实数,
因此,其数值范围为 `0 ~ 255`
---------------------------------------------------
`255 + 1`
  00000000000000000000000000000000`11111111`   255
+ 
  00000000000000000000000000000000`00000001`   1
-> 
  00000000000000000000000000000001`00000000`   0
 
`0 + -1`
  00000000000000000000000000000000`00000000`   0
+ 
  00000000000000000000000000000000`11111111`  -1
-> 
  00000000000000000000000000000000`11111111`   255
  
---------------------------------------------------
`其他整型家族类型跟char类型同理`
% 格式意义
%d打印十进制的有符号数字
%u打印十进制的无符号数字
%p打印传入的地址数据的值
数组,指针,函数进阶

# 数组,指针进阶

假如一个数组为int a[5]
其中int [5]为其数组类型,而a是其变量名。
   int  a[5]    -> 
   a[5] - `数组`  ->
   int - `整型`
-> 由于[]优先级较高,a与[]结合,因此变量`a`是个`数组`,剩下的`int`就为其类型,
   因此`int  a[5]`是`整型数组`,
   此数组里面存放5个int类型元素。
   int* pa[5]    ->
   pa[5] - `数组`  ->
   int* - `整型指针`
-> 由于[]优先级较高,pa与[]结合,因此变量`pa`是个`数组`,剩下的`int*`就为其类型,
   因此`int* pa[5]`是`指针数组`,
   此数组里面存放5个int*类型元素。
   int (*paa)[5]    ->
   *paa - `指针`      ->
   int [5] - `整型数组`
-> 将()看为一个整体,由于*与paa结合,因此变量`paa`是个`指针`,剩下的`int [5]`就为其类型,
   因此`int (*paa)[5]`是`数组指针`,
   此指针里面存放一个包含5个int类型元素的数组地址。
   
   int (*paaa[10])[5] ->
   paaa[10] - `数组`    ->
   (*)	- `指针`          ->
   int [5]  - `整型数组`
-> 将()看为一个整体,由于[]优先级较高,paaa先与[10]结合,因此,变量`paaa`是个`数组`,
   剩下的`int (*)[5]`就为其类型,因此`int (*paaa[10])[5]`是`数组指针数组`,
   此数组里面存放10个数组指针元素,每个数组指针里面存放一个包含5个int类型元素的数组地址。

# 函数,指针进阶

函数声明:int Add(int x, int y);
其中int为其返回类型,Add为函数名,()为函数操作符,
int x 和 int y 为传入参数的类型,
此外,Add 、x 和 y 为操作数。
当Add函数被调用时,会在系统内存中栈区选取一块空间来
存放Add函数,要想取得这块空间地址,可通过
printf("%p",  add);
printf("%p", &add);
这两种方法,回想一下数组的内存空间地址的获取,
倘若打印地址时传入的是变量名,则返回的会是`数组首地址`;
倘若打印地址时传入的是取址符(&)加变量名,则返回的会是整个数组的地址,
但函数不同,其取址符(&)获取到的地址与其直接为变量名的地址是一致的,
也就是说,获取地址时,函数变量不加取址符(&)与加取址符(&)都一样。
假如需要一块空间用于存放Add函数的地址,那可以创建一个
函数指针来存放该地址,可通过
int (*p)(int, int) = add;
int (*p)(int, int) = &add;
这两种方法来实现,另外需注意一种错误的写法
int* p(int, int);
由于()的优先级较高,其与p结合后相当于一个函数,
这种写法相当于一个返回整型指针类型的函数声明。
另外,倘若需要使用函数指针,那需要注意下面两种函数指针的`陷阱`了。
-----------------------------------------------------------
第一种是函数类型转换与函数调用的误区。
(*(void (*)())0)();
如上,这种陷阱写法中`void (*)()`为一个函数指针,
该指针指向的函数不需要参数并且也不返回值,
之后再通过`()`形式使`数字0`进行一次函数指针类型的`强制转换`,
例如,强制类型转换的例子
-----------
(int) a
(float) a
(char) a
-----------
这种形式,由此可见`数字0`被充当为一个用来存放调用函数的地址工具,
之后通过`*`号来解引用地址中存放的函数,
最后再通过()形式来调用此函数。
(*(void (*)())0)() -> (*(函数指针类型强制转换)0)() -> (*0)()
-----------------------------------------------------------
第二种是返回函数指针类型的函数声明。
void (*signal(int, void (*)(int)))(int);
如上,这种陷阱写法中`void (*)(int)`为函数指针的写法,
并且由于变量名加()可表示为函数,
可见`signal`是一个函数,`void (*)(int)`为其传入的形参,
此外假如将`signal(int, void (*)(int))`看成一个函数,
则上述陷阱写法可写成
void (*函数)(int)
这样看来,`void (*)(int)`只能为其返回类型了,
所以这是一个返回函数指针类型的函数声明。
void (*signal(int, void(*)(int)))(int) ->
void (*signal(形参, 形参))(int)	->
void (*函数名(形参, 形参))(int)	->
函数返回类型 函数名(形参, 形参)
-----------------------------------------------------------

# 函数,指针,数组进阶

假如此时需要实现一个简单的`计算器`逻辑,其中的`加、减、乘和除`已经实现了,
并命名为`Add,Sub,Mul,Div`,当动手实现时却发现一个问题,
倘若只是简单的使用`switch`或`if/else`语句,此时实现的代码即冗长又繁杂,
因此,通过将已经实现的函数方法储存起来再利用不失为一种简单有效的方法。
----------------------
int Add(int x, int y);
int Sub(int x, int y);
int Mul(int x, int y);
int Div(int x, int y);
----------------------
要想储存上述已经实现的函数,需要一个包含`4`个储存空间的数组,
并且存储的是函数的`地址`,可以通过定义一个函数指针数组
int (*p[4])(int, int) = {Add, Sub, Mul, Div};
这种方法,当想要调用数组中的函数时,可以通过
printf("%d", p[0](2,3));
printf("%d", (*p[0])(2,3));
这两种方法。
倘若此时需要实现的不再是一个简单的`计算器`,而是更加完整的`计算器`,
想必,多个`函数指针数组`是必不可少的,
此时还需要一个能储存不同类别数组的指针,
这个`指针`指向一个包含多个函数指针的数组,可以通过
int (*(*pa)[4])(int, int) = &p;
这种方法来定义一个`指向函数指针数组的指针`,由于定义方法较为复杂,
此处对其进行剖析。
对于`指向函数指针数组的指针`,已经明确了该变量类型为指针即`*pa`,
接着,因为这个指针是用于存放`一个函数指针的数组`,
因此,其类型为`int (*[4])(int, int)`。

# 数组,指针,函数

----
数组
------------------------------------------------------
1.假如有`4`个`整型`元素需要储存在变量`a`中,
可以写成`整型数组`,
数组存储的类型是4个整型元素各自的`值`。
int a[4] = {1, 2, 3, 4};
2.假如有`4`个`存储整型数组的数组`元素需要储存在变量`b`中,
可以写成`存储整型数组的数组`,
数组存储的类型是4个存储整型数组的数组各自的`首地址`。
int a1[4] = {1,2,3,4};
int b1[4] = {2,3,4,5};
int c1[4] = {3,4,5,6};
int d1[4] = {4,5,6,7};
int* b[4] = {a1, b1, c1, d1};
3.假如有`4`个`整型指针`元素需要储存在变量`c`中,
可以写成`整型指针数组`,
数组存储的类型是4个整型指针元素各自的`指针地址`。
int a2 = 1;
int b2 = 2;
int c2 = 3;
int d2 = 4;
int* pa2 = &a2;
int* pb2 = &b2;
int* pc2 = &c2;
int* pd2 = &d2;
int* c[4] = {pa2, pb2, pc2, pd2};
4.假如有`4`个`存储整型指针数组的数组`元素需要储存在变量`d`中,
可以写成`存储整型指针数组的指针数组`,
数组存储的类型是4个存储整型指针数组的数组元素各自的`指针地址`。
int a3[4] = {1,2,3,4};
int b3[4] = {2,3,4,5};
int c3[4] = {3,4,5,6};
int d3[4] = {4,5,6,7};
int* pa3[2] = {a3, b3};
int* pb3[2] = {b3, a3};
int* pc3[2] = {c3, b3};
int* pd3[2] = {d3, a3};
int** d[4] = {pa3, pb3, pc3, pd3};
5.假如有`4`个`函数`元素需要储存在变量`e`中,
可以写成`储存函数的数组`,
数组存储的类型是4个函数元素各自的`临时指针地址`。
int Add(int, int);
int Sub(int, int);
int Mul(int, int);
int Div(int, int);
int (*e[4])(int, int) = {Add, Sub, Mul, Div};
6.假如有`4`个`函数指针`元素需要储存在变量`f`中,
可以写成`储存函数的数组`,
数组存储的类型是4个函数指针元素各自的`指针地址`。
int Add1(int, int);
int Sub1(int, int);
int Mul1(int, int);
int Div1(int, int);
int (*A1)(int, int) = Add1;
int (*S1)(int, int) = Sub1;
int (*M1)(int, int) = Mul1;
int (*D1)(int, int) = Div1;
int (*f[4])(int, int) = { A1, S1 , M1 , D1 };
7.假如有`4`个`存储函数指针数组的数组`元素需要储存在变量`f`中,
可以写成`储存函数指针数组的数组`,
数组存储的类型是4个存储函数指针数组的数组元素各自的`函数指针数组的地址`。
int Add2(int, int);
int Sub2(int, int);
int Mul2(int, int);
int Div2(int, int);
int (*A2)(int, int) = Add2;
int (*S2)(int, int) = Sub2;
int (*M2)(int, int) = Mul2;
int (*D2)(int, int) = Div2;
int (*a4[2])(int, int) = { A2, S2 };
int (*b4[2])(int, int) = { S2, M2 };
int (*c4[2])(int, int) = { M2, D2 };
int (*d4[2])(int, int) = { D2, A2 };
int (*(*f[4]))(int, int) = { a4, b4, c4, d4 };
----
指针
------------------------------------------------------
1.假如有`1`个`整型数组的地址`需要储存在变量`pa`中,
可以写成`数组指针`,
指针存储的类型是`数组首地址`。
int a[] = {1,2,3,4};
int* pa = a;
2.假如有`1`个`存储整型指针的数组的地址`需要储存在变量`pb`中,
可以写成`指向整型指针的数组的指针`,
指针存储的类型是`传入的指针数组的地址`。
int a = 1;
int b = 2;
int c = 3;
int d = 4;
int* p_a = &a;
int* p_b = &b;
int* p_c = &c;
int* p_d = &d;
int* e[4] = {p_a, p_b, p_c, p_d};
int* (*pb)[4] = e;
3.假如有`1`个`函数的地址`需要储存在变量`pc`中,
可以写成`函数指针`,
指针存储的类型是`指针的地址`。
int Add(int, int);
int (*pc)(int, int) = Add;
4.假如有`1`个`存储函数指针的数组的地址`需要储存在变量`pd`中,
可以写成`指向函数指针的数组的指针`,
指针存储的类型是`传入指针的地址`。
int Add(int, int);
int Sub(int, int);
int Mul(int, int);
int Div(int, int);
int (*A)(int, int) = Add;
int (*S)(int, int) = Sub;
int (*M)(int, int) = Mul;
int (*D)(int, int) = Div;
int (*p[4])(int, int) = { A, S, M , D };
int (*(*pd)[4])(int, int) = &p;
结构体、枚举和联合体

# 结构体内存对齐

/*
结构体存在内存对齐规则,并且不同编译器都有不同的对齐数,
对于其存在的原因可能有这两种。
对齐规则:
1. 结构体中第一个变量内存存储在偏移量为零的地方。
2. 除第一个变量外,其他变量需存储在其对齐数的 `整数倍的位置`。
`对齐数` = 取 (变量类型字节与编译器规定的对齐数) 的最小值
vs 编译器的默认编译器为 8,(不确定,vs2022 测试为 4)
gcc 编译器没有对齐数,因此 gcc 中结构体的对齐数 = 变量类型字节。
3. 结构体的总字节大小为结构体中最大对齐数的整数倍。
4. 当结构体 1 中存在结构体 2 时,结构体 1 内的结构体 2 的字节为该结构体 2 的结构体字节数。
一是程序使用平台的可移植性:
当已写好的程序在其他硬件平台上使用时,
要考虑硬件对内存的调用方法,
可能存在硬件不能访问某些特定内存区间而导致异常反馈。
二是时效性的缘故:
通用寄存器通常一次能访问 4 个字节,
倘若结构体中的内存摆放是紧凑的,
寄存器读取一段后读取了一个变量和
另一个变量的四分之一字节,
这时寄存器需要再放回从这四分之一字节的首地址处
再读取一次,
倘若此时结构体的内存摆放是遵循内存对齐规则,
则寄存器能一次性读取,不需要再往返读取。
*/
#include<stdio.h>
struct T1
{
	char a; //0		(1/4 -> 1 对齐数)
	//1-3 为浪费的内存空间
	int b; //4-7	(4/4 -> 4 对齐数)
	char c;//8		(1/4 -> 1 对齐数)
};//12  结构体中最大对齐数为 4
struct T2
{
	int a;//0-3  (4/4 -> 4 对齐数)
	char b;//4   (1/4 -> 1 对齐数)
	char c;//5   (1/4 -> 1 对齐数)
};//8  结构体中最大对齐数为 4
struct T3
{
	int a;//0-3  4/4 -> 4
	struct T2 t2;//4-11 8/4 -> 4 0 - 7
	struct T1 t1;//12-23 12/4 -> 4 8-19
	char b;//24 1/4 -> 1
};//28 结构体中最大对齐数为 4
int main()
{
	struct T1 t1 = { 0 };
	struct T2 t2 = { 0 };
	printf("%d\n", sizeof(t1));//12 (9 不是 4 的整数倍,多浪费 3 个字节空间)
	printf("%d\n", sizeof(t2));//8 (6 不是 4 的整数倍,多浪费 2 个字节空间)
	printf("%d\n", sizeof(t3));//28 (25 不是 4 的整数倍,多浪费 3 个字节空间)
	return 0;
}
// 倘若需要自己定义编译器的对齐数时,可通过 #pragma pack (对齐数) 来实现
#include<stdio.h>
#pragma pack(2)// 设置默认对齐数为 2
struct T1
{
	char a;//0   1/2 -> 1
	int b;//2-5  4/2 -> 2
	char c;//6   1/2 - >1
};//8 最大对齐数为 2
#pragma pack() // 此处作用为取消设置对齐数
int main()
{
	struct T1 t1 = { 0 };
	printf("%d\n", sizeof(t1));//8 (7 不是 2 的整数倍,多浪费 1 个字节)
	return 0;
}

# 结构体中的柔性数组 (C99 标准)

/*
C99 标准中允许结构体中最后一个变量为未知大小的数组,
并且求结构体字节时该数组不会被包括。
struct tag
{
	int n;
	int a [0];
};
struct tag
{
	int n;
	int a [];
}
*/
// 柔性数组的动态内存分配
#include<stdio.h>
#include<stdlib.h>
struct T1
{
	int n;
	int a[0];
};
int main()
{
	struct T1 t1;
	struct T1* st1 = (struct T1*)malloc(sizeof(struct T1) + 5 * sizeof(int));
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d\n", st1->a[i] = i);
	}
	return 0;
}

# 位段

/*
位段与结构体的形式相似,
位段可以有效利用所给的内存空间,
不过位段中的元素类型必须是整型家族成员,
此外位段大小不应该超过其数据类型的最大字节。
当程序考虑可移植性的问题时,
由于位段的不确定性因素,不推荐使用位段。
当位段程序进行跨平台时,具有以下几个问题
1.int 类型被当成有符号或无符号类型是不确定的
2. 所跨平台操作系统的位数是不确定的,例如 16 位或 32 位
3. 由于大小端操作系统的缘故,内存分配是从右向左还是相反都不确定
4. 当开辟的第一个数据类型空间无法容纳第二个变量时,
内存是会重新开辟空间或者是填充完第一个数据类型空间后再开辟空间填充,
对于这两种情况是不确定的。
位段的表示形式
struct tag
{
	变量类型 变量名:所占的比特位空间
};
*/
#include<stdio.h>
struct T1
{
	//int 为 4 个字节即 32 个比特位
	int a : 4;// 占用 int 类型的 4 个字节中的 4 个比特位 
	int b : 4;// 占用 int 类型的 4 个字节中的 4 个比特位
	// 由于上方 int 空间不足 32 个字节,创建新的 int 空间
	int c : 32;// 占用 int 类型的 4 个字节中的 32 个比特位
	// 由于上方 int 空间不足 6 个字节,创建新的 int 空间
	int d : 6;// 占用 int 类型的 4 个字节中的 6 个比特位 
};
int main()
{
	struct T1 t1;
	printf("%d\n", sizeof(t1));//12
	return 0;
}
/*
由于 a 与 b 为 1 个字节,但 c 需要存储 32 个比特位,
故重新开辟了一个 int 内存空间,
a 与 b 所处的 int 内存空间的空闲空间被浪费,
此外,d 由于 c 所处 int 内存空间不足,
故又重新开辟一块 int 内存空间,
因此总共开辟了 3 个 int 内存空间,
故占了 12 个字节。
*/

# 枚举的优点

/*
当讨论枚举相较于 #define 时,
常常谈到 #define 也可以实现枚举的操作,
那这就表明 #define 就明显优于枚举吗?
答案是否定的。
1. 枚举可以一次性创建多个常量的特性明显由于 #define。
2. 此外由于 #define 的操作是完全替换,
当维护代码时枚举的修改操作次数会少于 #define 的修改操作次数,
这使代码调试时更加方便。
3.#define 的标识符是全局替换,而枚举常量是有类型检查,并且不会影响
工程中的其他重名变量或函数,这有效的避免了命名污染。
*/
#include<stdio.h>
// 枚举类型,可列举的常量
enum T1
{
	A,
	B,
	C,
	D
};
int main()
{
	enum T1 t1;
	//0 1 2 3
	printf("%d %d %d %d\n", A,B,C,D);
	return 0;
}
// 自定义顺序
#include<stdio.h>
enum T2
{
	A,
	B = 255,
	C,
	D = 244,
	E
};
int main()
{
	enum T1 t1;
	printf("%d %d %d %d %d\n", A,B,C,D,E);0 255 256 244 245
	return 0;
}

# 联合 - 联合体 - 共用体

/*
联合体类似于结构体,
都需要内存对齐,
但联合体中的内存空间是共用的,
联合体的字节大小至少是联合体中具有最大字节的成员的大小,
并且为该联合体中最大对齐数的整数倍。
*/
#include<stdio.h>
union T1
{
	int a;// 最大成员与最大对齐数
	char b;
	short c;
};
union T2
{
	int a;// 最大对齐数
	char b[5];// 最大成员
	short c;
};
int main()
{
	union T1 t1 = {0};
	union T2 t2 = {0};
	printf("%d\n", sizeof(t1));//4
	printf("%d\n", sizeof(t2));//8
	return 0;
}