C语言---自定义类型:结构体(2)https://developer.aliyun.com/article/1544451
那么为什么会存在内存对齐的问题呢
- 平台原因 (移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定
类型的数据,否则抛出硬件异常。
- 性能原因:
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两个8字节内存块中。
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
内存对齐可以提升读取的效率
所以内存对齐还是很有必要的
那么在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到?
让占用空间小的成员尽量集中在一起
//那我们只能眼睁睁的看着字节被浪费吗? /* 我们是否有办法使内存少浪费一些 那么在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到? 让占用空间小的成员尽量集中在一起 */ /*这个时候我们就可以拿出之前的S1和S2了 struct S1 { char c1; int i; char c2; }; struct S2 { char c1;//对齐数是1 char c2;//对齐数是1 int i; }; 这两个类型成员是一样的,只是内部成员顺序不同 S1占12个字节 S2占8个字节 不难发现,我们将占用空间较小的成员集中放在一起, 原本要浪费的空间就被利用起来了 */
修改默认对齐数
pragma这个预处理指令,可以改变编译器的默认对齐数
//vs上默认对齐数是8 #pragma pack(1)//将默认对齐数设置为1 struct S { char c1; int n; char c2; }; #pragma pack()//恢复默认对齐数,恢复默认对齐数为8 //如果想恢复默认对齐数,我们pack()括号内可以直接不写 int main() { //printf("%zd\n", sizeof(struct S));//12个字节---默认对齐数为8的时候 printf("%zd\n", sizeof(struct S));//6个字节-=--默认对齐数是1的时候 return 0; }
3.结构体传参
//结构体传参 struct S//我们必须将这个结构体类型的创建放在前面,不然会报错的 { int arr[1000]; int n; char ch; }; void print1(struct S tmp)//传过来的是一个结构体变量,那么我们就创建一个结构体变量进行接收 { for (int i = 0; i < 10; i++) { printf("%d ", tmp.arr[i]); } printf("\n"); printf("n=%d\n", tmp.n); printf("ch=%c\n", tmp.ch); } void print2(const struct S* ps)//传过来的是结构体变量的地址,那么我们就创建一个结构体指针进行接收 {//那么ps就指向了s for (int i = 0; i < 10; i++) { printf("%d ", ps->arr[i]); } printf("\n"); printf("n=%d\n", ps->n); printf("ch=%c\n",ps->ch ); } int main() { struct S s = { {1,2,3,4,5,6,7,8,9,10},10,'w' };//创建结构体变量s并进行初始化 //利用函数将结构体内数据打印出来 print1(s);//直接将结构体变量s传过去-----传值调用 print2(&s);//直接将结构体变量s的地址传过去 return 0; } //分析:那种更好一些 /* 在print1中,我们传过去的是结构体变量, 相当与传值调用 在调用的时候,我们还要创建一个结构体变量tmp用来接收传过来的数据 在这个调用的时候,我们还要额外的空间进行结构体变量的创建 假如传过来的变量占用空间很大,我们又要创建一个一模一样的空间进行存储 我认为这么很浪费空间,还很消耗时间 */ /* 但是我们利用print2传的仅仅只是地址,地址的大小是4个字节或者8个字节 我们在创建形参的时候仅仅只需要创建一个指针ps进行接收, 指针ps就能找到这个结构体变量内的数据 这样也不需要创建结构体指针变量进行数据的拷贝,我们直接利用地址进行数据的调用和访问 */ //传值能做到的,传地址一定能做到 //传地址能做到的,传值不一定能做到 //所以我们在结构体传参的时候,要传结构体的地址 //如果不想因为地址传过去的原因,地址指向的数据被改变了,我们直接加上const
在我们进行结构体传参的时候,我们传地址就行了
4.结构体实现位段
结构体讲完就得讲讲结构体实现位段的能力
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是int 、unsigned int 或signed int,在C99中位段成员的类型也可以选择其他类型
2.位段的成员名后边有一个冒号和一个数字
位段中的位指的是二进制中的位
//二进制中一位就是一个比特位 //一个字节是8个比特位 struct S { /*int _a:2;*///_a占2个比特位 /* 假设我们在设定a的值的时候,我们只想要a是0 1 2 3 这4个数字 那么二进制位表达出来就是 0---00 1---01 2---10 3---11 这四个数刚好满足两个比特位的要求 如果我们给a32个比特位的话就有点浪费空间了 */ int _a : 2; int _b:5;//_b占5个比特位 int _c:10;//_c占10个比特位 int _d : 30;//_d占30个比特位 //int _e : 50;//这种写法是错的,对于一个整型来说,最大也就32个比特位,没有50 }; int main() { printf("%zd\n", sizeof(struct S));//8 /* 如果不进行位段的调整,将是16个字节 */ return 0; } //这种设计相当于限制了大小,可以减小空间,避免空间浪费 //8个比特位等于1个字节
通过位段的使用,可以减少空间的占有,减少空间浪费
限制数据的长度、大小
节省空间
C语言---自定义类型:结构体(4)https://developer.aliyun.com/article/1544455