- 结构体类型的声明
- 结构体的自引用
- 结构体内存对齐
- 结构体传参
自学b站“鹏哥C语言”笔记。
一、结构体类型的声明
详见文章【初识结构体】第一部分。补充说明:
匿名结构体类型:省略结构体标签(tag)
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}* p;
int main()
{
p = &x;
return 0;
}
注意:上述代码会报错。因为编译器认为这两个匿名结构体是不同的,则p = &x;
会出错。
二、结构体的自引用
详见文章【初识结构体】第二部分。补充说明:
错误示例:
struct Node
{
int data;
struct Node 0;
};
注意:上述结构体Node的存储空间不可知。
正确示例:运用指针
struct Node
{
int data;
struct Node* next;
}
注意:自引用不能使用匿名结构体类型,否则会出错。
三、结构体内存对齐
1.结构体内存对齐的规则
(1)结构体第一个成员放在与结构体变量偏移量为0的地址处。
(2)结构体其他成员要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = min { 编译器默认的一个对齐数,该成员大小 }
VS中默认的对齐数是8
(3)结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
(4)特例:嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体总大小是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
例1:一般情况
#include <stdio.h>
struct S1
{
char c1;
int a;
char c2;
}
struct S2
{
char c1;
char c2;
int a;
}
int main()
{
struct S1 s1 = {0};
struct S2 s2 = {0};
printf("%d\n", sizeof(s1));
printf("%d\n", sizeof(s2));
}
输出结果:12 8
解析:
char是1字节,int是4字节,VS中默认的对齐数是8。
那么,c1对齐数=min{1,8}=1
a对齐数=min{4,8}=4
c2对齐数=min{1,8}=1
S1的内存:第一行的数字指的是靠左这条线上的指针
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
c1 | 空 | a | c2 |
最大对齐数=max{1,4,1}=4
目前,所有成员所占大小为9字节,不是最大对齐数的倍数。
因此,应补齐到12字节。
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
c1 | 空 | a | c2 | 空 |
同理,S2的内存:
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
c1 | c2 | 空 | a |
最大对齐数=max{1,4,1}=4
目前,所有成员所占大小为8字节,是最大对齐数的倍数。
因此,S2所占内存就是8字节。
例2:结构体嵌套情况
#include <stdio.h>
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
int main()
{
printf("%d\n", sizeof(struct S4));
}
输出结果:32
解析:
先计算S3:
double是8字节,char是1字节,int是4字节,VS中默认对齐数是8。
那么,double对齐数=min{8,8}=8
char对齐数=min{1,8}=1
int对齐数=min{4,8}=4
最大对齐数=max{8,1,4}=8
S3内存:和例1同理,得16字节
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
d | c | 空 | i |
再计算S4:
char是1字节,S3是16字节,double是8字节,VS中默认对齐数是8。
那么,char对齐数=min{1,8}=1
S3对齐数=S3自己的最大对齐数=8
double对齐数=min{8,8}=8
最大对齐数=max{1,8,8}=8
S4内存:32字节
0 | 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 | 29 | 30 | 31 | 32 |
c1 | 空 | s3 | d |
2.内存对齐的意义
为什么存在内存对齐?
- 平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的,即某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。比如,可能存在平台只能在地址为4的倍数处读取int类型的值。
- 性能原因:数据结构,尤其是栈,应该尽可能在自然边界上对齐。因为访问未对齐的内存,处理器需要作两次内存访问,而对齐的内存只需要一次访问。
总结:结构体的内存对齐是用空间换取时间的做法。
3.设计结构体
一个好的结构体设计,要能做到既节省空间又节省时间。
节省时间已经通过内存对齐实现了。
我们需要在满足内存对齐的前提下,尽量节省空间:让占用空间小的成员尽量集中在一起。
4.修改默认对齐数
#pragma
可以更改默认对齐数。
#pragma pack(4)//设置默认对齐数为4
struct S
{
char c1;
double d;
}
#取消设置的默认对齐数
5.宏
offsetof(struct tag, 成员变量名)
能够输出成员相对首地址的偏移量,引用前要写#include <stddef.h>
。
例1:
#include <stdio.h>
#include <stddef.h>
struct S
{
char c;
int i;
double d;
}
int main()
{
printf"%d\n", offsetof(struct S, c);
printf"%d\n", offsetof(struct S, i);
printf"%d\n", offsetof(struct S, d);
return 0;
}
输出结果:0 4 8
四、结构体传参
结论:结构体传参的时候,优先选择传地址。
详见文章【初识结构体】。补充例题:
例1:
struct S
{
int a;
char c;
double d;
}
void Init(struct S tmp)
{
tmp.a = 100;
tmp.c = 'w';
tmp.d = 3.14;
}
int main()
{
struct S s = {0};
Init(s);
return 0;
}
运行后s仍然都是0。原因是传参传的是s值,改的是tmp,并没有改变s。
改进:
struct S
{
int a;
char c;
double d;
}
void Init(struct S* ps)
{
ps->a = 100;
ps->c = 'w';
ps->d = 3.14;
}
int main()
{
struct S s = {0};
Init(&s);
return 0;
}
运行后s改变了。原因是传参传的是s的地址,改变tmp的同时,也改变了s。
改进:打印出改变后的s值
#include <stdio.h>
struct S
{
int a;
char c;
double d;
}
void Init(struct S* ps)
{
ps->a = 100;
ps->c = 'w';
ps->d = 3.14;
}
void Print1(struct S tmp)
{
printf("%d %c %lf\n", tmp.a, tmp.c, tmp.d);
}
void Print2(struct S* ps)
{
printf("%d %c %lf\n", ps->a, ps->c, ps->d);
}
int main()
{
struct S s = {0};
Init(&s);
Print1(s);
Print2(&s);
return 0;
}
注意:
标签:struct,自定义,int,数据类型,C语言,char,对齐,结构,字节 From: https://blog.51cto.com/u_15883132/5995699Print1函数的传参传的是s的值,可以实现。原因是该函数的目标不需要改变s的值。
当然,用传递s的值来实现也是可以的,如Print2函数。