一、结构体


1.匿名结构体类型

我们来举个例子介绍匿名结构体

1
2
3
4
5
6
7
struct  //创建时只有关键字,即为匿名结构体
{
char a;
int b;
char c;
double d;
}s;

再来列举一个匿名结构体常见的错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct  
{
char a;
int b;
char c;
double d;
}s;

struct
{
char a;
int b;
char c;
double d;
}*p; //用p指向这个匿名结构体

int main()
{
p = &s; //观察两个匿名结构体发现组成成分一样,是不是就可以通过这种方式使 p 指针存储 s 呢?
//error 虽然类型一样,但是在编译器看来这是两个不同的匿名结构体,非法
return 0;
}

2.结构体的自引用

举个例子来看看结构体如何相互引用

1
2
3
4
5
6
7
8
9
10
11
12
struct A
{
int i;
char c;
};

struct B
{
char c;
struct A sa; //结构体类型 struct B 中含有结构体类型 struct A;
double b;
};

那么结构体自引用是否为以下这种方式呢

1
2
3
4
5
struct N
{
int d;
struct N n; //error 无限套娃 ,如果计算大小就直接爆炸
};

正确方法如下

1
2
3
4
5
struct N
{
int d;
struct N* next; //通过指针找到同类型的下一节点
};

3.结构体的大小计算

结构体的对齐规则

1.第一个成员在与结构体偏移量为0的地址处
2.其他成员变量要对齐到(对齐数)的整数倍的地址处
.
对齐数 = 编译器默认的对齐数 与 该成员大小的较小值
VS默认为8(并且该默认值可以通过编码修改)
.
3.结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍
4.如果该结构体嵌套了另一个结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处
结构体整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍


我们以一张图来整明白这些话


前面提到过可以修改默认对齐数

以下

1
2
3
4
5
#pragma pack()  //括号内的数字即是你要的默认对齐数
/
/
//使用完结构体后再恢复默认对齐数为8=
#pragma pack() //括号内不放数字

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#pragma pack(2)
struct A
{
char a;
int b;
char c;
};
#pragma pack()

int main()
{
printf("%d\n",sizeof(struct A));
return 0;
}

二、位段

1.位段的介绍

位段的声明和结构体是类似的,但有两个不同
1.位段的成员必须是int、unsigned int 或 signed int
2.位段的成员后边有一个冒号和一个数字

例如

1
2
3
4
5
6
7
struct A
{
int a:2; //a成员占2bit位
int b:5; //b成员占5bit位
int c:10; //c成员占10bit位
int d:30; //d成员占30bit位
} //此时A就是一个位段类型

为什么好好的一个整形,偏要把它硬塞进2bit呢?
节省空间!!

比如说表示性别,2个字节就足矣

00 保密
01
11

2.位段的大小

还是以上述代码举例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//2+5+10+30=47
//识别出int,创建一个int的大小,即32bit
struct A
{
//创建4字节----32bit
int a:2; //a成员占2bit位
int b:5; //b成员占5bit位
int c:10; //c成员占10bit位

//还留下15bit
//d塞不进去了 再创建4字节 ---32bit
int d:30; //d成员占30bit位
}

int main()
{
printf("%d\n",sizeof(struct A)); //结果为4+4 = 8字节
return 0;
}

但其实,在不同的环境下结果可能是不同的

比如在上述代码中,d塞不进去后,是继续用上一次留下的15bit,还是直接塞进新创建的呢?

所以说

位段涉及到很多不确定因素,位段是不跨平台的
注重可移植的程序应该避免位段


3.位段的实际使用情况

1
2
3
4
5
6
7
8
9
10
11
12
struct S
{
char a:3;
char b:4;
char c:5;
char d:4;
};
struct S s = {0};
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;


以上


4.位段跨平台问题

1.int位段被当成有符号数还是无符号数是不确定的
2.位段中的最大位的数目不能确定(16位机器最大16,32位最大32,若写成大于16的数再16位机器就会出问题)
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义
4.当一个结构体包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时
—是舍弃剩余的位还是利用,这是不确定的


三、联合(共用体)


1.联合类型的定义

联合也是一种特殊的自定义类型
这种类型定义的变量也包含一系列的成员
特征是这些成员共用一块空间(所以联合也叫共用体)
.
所以联合体的大小最小是最大成员的大小


2.利用联合特征判断机器大小端字节序

1
2
3
4
5
6
7
8
9
10
11
12
13
int check_sys()
{
union U
{
char c;
int i;
}u;
u.i = 1; // 00 00 00 01
// 01 00 00 00
return u.c;
//返回1是小端
//返回0是大端
}

3.联合大小的计算

联合大小最小是最大成员的大小
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍

比如

1
2
3
4
5
6
7
union Un
{
char arr[5]; //对齐数为1
int b; //对齐数为4
//又因为至少为5(arr为最大成员)
//所以对齐至8
};