初始化和使用
struct Books {
char title[50];
char author[50];
char subject[100];
int book_id;
};
可以将一个结构体变量作为一个整体赋值给另一相同类型的结构体变量,可以到达整体赋值的效果;这个成员变量的值都将全部整体赋值给另外一个变量
struct Books book = {.....};
// or
book.book_id = 1;
数据类型
struct Books 是一个数据类型,struct Books 的含义和 int 是一样的,所以有下面代码
struct Books * struct_pointer = &book;
访问结构体成员
book.book_id = 1;
//OR
struct_pointer -> book_id = 1;
struct_pointer -> book_id = 1 会被转化为 *(struct_pointer ).book_id ,在 C 语言中结构体数据的访问也是通过指针偏移来完成的。
结构体的内存
struct test_struct
{
char a;
short b;
char c;
int d;
char e;
};
C/C++ 的特点就是所有 declarative knowledge 都被丢弃了。剩下的全是 imperative instructs 怎么去用这些 declarative knowledge 的行为,而这些行为都是 compile time 就定的。所以「定义一个结构体,只是告诉编译器结构体变量内部的布局。并不会生成在实际的代码或内存中」。
这就得出一个结论只有在结构体实例化的时候才会占用内存,他们和基本数据类型int,char等一样,具体在内存哪个区域看声明的区域,在函数内的局部变量,则在栈上,静态变量或全局变量则在静态存储区,new/malloc的,都在堆上存储。(一个结构体同它内部的属性是一块内存)
结构体内存计算
一般来说,结构体大小,不等于内部成员的大小之和。结构体元素的对齐和内存分配地址的对齐本质上都是为了CPU访问数据更快,更容易预取和cache,(是为了防止出现 cpu 读取一个数据读取两次的情况)这是有不同CPU的设计原理决定的。然后编译器也有自己的一些习惯,比如VC和GCC的对齐方式就不太一样。而且,对齐可以被编译参数已经配置改变,并不是固定的。
原则一:结构体中元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每一个元素放置到内存中时,它都会认为内存是以它自己的大小来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始(以结构体变量首地址为0计算)。
#include <stdio.h>
struct A{
char a;//1
int b;//4
char c;//1
int* d;//8
};
int main(){
printf("%d\n", sizeof(struct A));//24
return 0;
}
- 环境:win 64位,即机器读取内存,一次读取64位-8字节。
- 首先,按照定义顺序,先放char a在地址0,char占一个字节;
- 其次,存储int b,放在模4为空的第一个位置,即地址4,int占4个字节,地址编号4 5 6 7的内存空间都是Int b 的空间,变量a和b中间的地址为1 2 3的空间为填充占位;
- 然后,存放char c,放在地址8,char占一个字节;
- 最后,指针变量d,指针8个字节,找模8为空的第一个位置,地址0不行,地址8不行,地址16可以,因此地址16到23存放指针d,其中c和d之间的地址编号为9-15的为占位空间。
#include<stdio.h>
struct A{
char c;//1
char b;//1
int* d;//8
int a;//4
};
int main(){
printf("%d\n", sizeof(struct A));//24
return 0;
}
按照上述计算,得到20,而程序输出24,由此引出原则2:
原则二:在经过第一原则分析后,检查计算出的存储单元是否为所有元素中最宽的元素的长度的整数倍,是,则结束;若不是,则补齐为它的整数倍。最后放int a,需要补齐8个字节来对齐。
总结:结构体大小未必等于成员大小的和;按照内存对齐,成员位置在模自己长度为0的第一个空位;最后,需要按照最宽成员进行内存补齐。因此,为了节省结构体所占内存空间:可以按照元素需要内存的大小排序,降序排序,并且同类型的尽量临近声明。
int 的大小
x86体系对于64位系统依然选择了32位的int,这在当年amd K8的白皮书里面有说,他们认为32位int的适用性更广,效率更高,所以坚持在64位指令集中依然主推32位int,用多少字长的int主要还是cpu架构决定。
offsetof
#include <stddef.h>
typedef struct {
char a;
} Align;
int main() {
Align align = {};
offsetof(Align, a);
}
#define offsetof(s,m) ((size_t)&(((s*)0)->m))
由于 offset 是宏函数,所以以允许传入结构体的类名,将 NULL 转换为 Align* 类型的指针,然后访问 a,然后取地址得到就是地址相对于 0 的偏移量