目录
结构体
1.1 结构的基础知识
1.2 结构体的声明
1.3 特殊的声明
1.4 结构的自引用
1.5 结构体变量的定义和初始化
1.6 结构体内存对齐
1.7 修改默认对齐数offsetof ——宏
1.8 结构体传参
结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
//结构体的声明
struct Stu //结构体关键字struct , tag结构体标签名
{
char name[20];//成员列表
int age;
double score;
};//s1,s2,s3;//结构体变量列表
struct Stu s1;//全局结构体变量
struct Book
{
char name[20];
float price;
char id[20];
};
int main()
{
struct Stu s2,s3;//局部结构体变量
struct Book b1;
return 0;
}
//匿名结构体类型__必须加上成员列表,且只能使用一次
struct
{
char name[20];
float price;
char id[20];
}ss;
//匿名结构体类型
struct
{
int a;
char b;
float c;
}x;
struct
{
int a;
char b;
float c;
}a[20], *p;
//在上面代码的基础上,下面的代码合法吗?
p = &x;
匿名结构体的成员如果一样,但是在编译器看来也是不同类型的结构体
在结构中包含一个类型为该结构本身的成员是否可以呢?
//定义一个链表的结构体
//typedef就是类型重命名
typedef struct Node
{
int data;//数据域
//struct Node n;//err 这里会无限递归下去,无法知道n有多大
struct Node* next;//指针域
}Node;
int main()
{
struct Node n;
Node n2;
return 0;
}
typedef struct
{
int data;
node* next;//err__匿名结构体重命名为node,结构体有了才重命名
//而里面还没有node
}node;
int main()
{
//struct node n;
node n2;
return 0;
}
//结构体变量的定义及初始化
struct Book
{
char name[20];
float price;
char id[12];
}s = {"C语言",55.5f,"CYN001"};
struct Node
{
struct Book b;
struct Node* next;
};
int main()
{
struct Book s2 = { "结构体",66.6f,"JGT001" };
struct Node n = { {"java",77.7f,"java001"},NULL };
return 0;
}
//结构体内存对齐
//vs编译器默认的一个对齐数为8
//对齐数的计算 = 编译器默认的一个对齐数 与 该成员所占空间大小的较小值
struct S1
{
char c1;//1 8 ——对齐数为1 第一个成员在结构体变量偏移为0的地址处
int i;//4 8 ——对齐数为4 其他成员要对齐到对齐数的整数倍地址处
char c2;//1 8 ——对齐数为1
//结构体的总大小 = 最大对齐数(每个成员都有对齐数)的整数倍
};
struct S2
{
char c1;//1 ——对齐数为1
char c2;//1 ——对齐数为1
int i;//4 ——对齐数为4
//结构体总大小为 8
};
int main()
{
struct S1 s;
struct S2 ss;
printf("%d\n", sizeof(s));//12个字节
printf("%d\n", sizeof(ss));//8个字节
return 0;
}
考点
如何计算?
首先得掌握结构体的对齐规则:
- 1. 第一个成员在与结构体变量偏移量为0的地址处。
- 2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的值为8,Linux环境下没有默认对齐数,没有默认对齐数,成员变量自身大小就是它的对齐数
- 3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
//练习
struct S3
{
double d;//8
char c;//1 ——对齐数1
int i;//4 ——对齐数为4
};
int main()
{
printf("%d\n", sizeof(struct S3));//16
return 0;
}
//嵌套结构体大小计算
struct S1
{
char c1;//1 8 ——对齐数为1 第一个成员在结构体变量偏移为0的地址处 0
int i;//4 8 ——对齐数为4 其他成员要对齐到对齐数的整数倍地址处 4
char c2;//1 8 ——对齐数为1 8
//结构体的总大小 = 最大对齐数(每个成员都有对齐数)的整数倍
};
struct S2
{
char c1;//1 ——对齐数为1 0
char c2;//1 ——对齐数为1 1
int i;//4 ——对齐数为4 4
//结构体总大小为 8
};
struct S3
{
double d;//8
char c;//1 ——对齐数1
int i;//4 ——对齐数为4
};
struct S4
{
char c1;//1
struct S3 s3;//16 自身最大对齐数为8
double d;//8
};
#include
int main()
{
//如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,
//结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
printf("%d\n", sizeof(struct S4));
return 0;
}
为什么存在内存对齐?
大部分的参考资料都是如是说的:
- 1. 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特 定类型的数据,否则抛出硬件异常。
- 2. 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总体来说:
结构体的内存对齐是拿空间来换取时间的做法。
那在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
让占用空间小的成员尽量集中在一起。
//例如:
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
size_t offsetof( structName, memberName );
头文件
struct S1
{
char c1;//1 8 ——对齐数为1 第一个成员在结构体变量偏移为0的地址处 0
int i;//4 8 ——对齐数为4 其他成员要对齐到对齐数的整数倍地址处 4
char c2;//1 8 ——对齐数为1 8
//结构体的总大小 = 最大对齐数(每个成员都有对齐数)的整数倍
};
struct S2
{
char c1;//1 ——对齐数为1 0
char c2;//1 ——对齐数为1 1
int i;//4 ——对齐数为4 4
//结构体总大小为 8
};
struct S3
{
double d;//8
char c;//1 ——对齐数1
int i;//4 ——对齐数为4
};
//offsetof——宏
//计算结构体成员相对于起始位置的偏移量
//头文件
#include
int main()
{
printf("%u\n", offsetof(struct S3, d));//0
printf("%u\n", offsetof(struct S3, c));//8
printf("%u\n", offsetof(struct S3, i));//12
struct S1 s;
printf("%u\n", offsetof(struct S1, c1));//0
printf("%u\n", offsetof(struct S1, i));//4
printf("%u\n", offsetof(struct S1, c2));//8
struct S2 ss;
printf("%u\n", offsetof(struct S2, c1));//0
printf("%u\n", offsetof(struct S2, c2));//1
printf("%u\n", offsetof(struct S2, i));//4
printf("%d\n", sizeof(s));//12个字节
printf("%d\n", sizeof(ss));//8个字节
printf("%d\n", sizeof(struct S3));//16
return 0;
}
//修改默认对齐数
//#pragma pack(4)修改默认对齐数为4
struct S1
{
char c;//1 对齐数1
double d;//8 对齐数8 计算的大小为16
};
#pragma pack(4)
struct S2
{
char c;//1 对齐数1
double d;//8 对齐数4 计算的大小为12
};
#pragma pack()
int main()
{
struct S1 s;
printf("%u\n", sizeof(struct S1));//16
printf("%u\n", sizeof(struct S2));//12
return 0;
}
结论:
结构在对齐方式不合适的时候,我们可以自己更改默认对齐数。
写一个宏,计算结构体中某变量相对于首地址的偏移,并给出说明
考察: offsetof 宏的实现
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体,形参是实参的一份临时拷贝,浪费空间
print2(&s); //传地址,仅仅传得是一个地址的大小4/8个字节
return 0;
}
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的 下降
结论:
结构体传参的时候,要传结构体的地址。