pp下载安装 app,杭州网站优化咨询,制作图片下载什么软件,公司的网站怎么做推广方案文章目录 #x1f4dd;前言#x1f320; 结构体内存对齐#x1f309;内存对齐包含结构体的计算#x1f320;宏offsetof计算偏移量#x1f309;为什么存在内存对⻬?#x1f320; 结构体传参#x1f6a9;总结 #x1f4dd;前言
本小节#xff0c;我们学习结构的内存对… 文章目录 前言 结构体内存对齐内存对齐包含结构体的计算宏offsetof计算偏移量为什么存在内存对⻬? 结构体传参总结 前言
本小节我们学习结构的内存对齐理解其对齐规则内存对齐包含结构体的计算使用宏offsetof计算偏移量为什么要存在内存对齐最后了解结构体的传参文章干货满满学习起来吧 结构体内存对齐
结构体内存对齐指的是结构体中各成员变量在内存中的存储位置按照一定规则对齐。 既然是按照一定规则那得首先了解它的对齐规则
结构体的第一个成员对齐到和结构体起始位置偏移量为0的地址处。其他成员变量要对齐到某个数字对齐数的整数倍的地址处。 对齐数 编译器默认的一个对齐数 与 该成员变量大小的较小值。
VS 中默认的值为 8linux 中gcc没有默认对齐数对齐数就是成员自身的大小
结构体总大小为最大对齐数结构体中的每一个成员都有一个对齐数所有对齐数中的的整数倍。如果嵌套了结构体的情况嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体中成员的对齐数的整数倍。 来代码理解
struct S1
{char c1;char c2;int i;
};struct S2
{char c1;int i;char c2;
};int main()
{printf(%d\n, sizeof(struct S1));printf(%d\n, sizeof(struct S2));return 0;
}代码运行
分析 首先结构体S1的成员有三个根据对齐规则结构体的第一个成员对齐到结构体变量起始位置偏移量为0的地址处-—C1放在偏移量为0的地址处接下来第二个C2就从第2个规则按对齐数进行放置C2的字节数char类型大小为1VS的默认对齐数为8对齐数取的是默认对齐数和成员变量字节大小的较小值18取1为对齐数然后偏移量为1的位置放1此时再看第三个变量i的字节大小为448对齐数为4当放在偏移量为2时2不是4的整数倍跳过3也不是跳过而当偏移量为4时刚好是4的整数1倍4*14然后占据为4个字节空间从偏移量0到最后偏移量的空间就是结构体的总大小为8此时还没有结束要验证根据第三条规则结构体的总大小为最大对齐数的整数倍最大对齐数为4411而结构刚才计算出来是8刚好是4整数倍4*2当这些都符合了结构体的大小就是8了。 一个例子你可能想是不是碰巧那么第二个例子 结构体S2中有三个成员C1大小为一第一个成员放在偏移量为0处第二个成员i大小为4偏移量123都不是4的整数倍然后这些空间都跳过不放数据注他开辟了空间但他此时不用你可能会想这不浪费吗文章我们慢慢解释然后偏移量为4时为整数倍从偏移量4开始放i直到7第三个元素C2大小为11的整数倍任何数的整数倍可以直接放当放在偏移量8处时全部成员都放完了我们还要对他进行验证是否为整数倍。S2最大对齐数是4偏移量9,10都不对当偏移量为11从0到11刚好为12为4的倍数4*312。所以S2总大小为12 内存对齐包含结构体的计算
struct S3
{double d;char c;int i;
};
int main()
{printf(%zd\n, sizeof(struct S3));return 0;
}运行结果16分析 首先第一个成员为d放在偏移量为0处double类型大小为8位置范围为0 ~ 7第二个成员C ,类型为char大小为118对齐数为1,1可以直接放占据8位置处第三个成员i大小为4,48对齐数是4,偏移量9,10,11都不是4的倍数12开始占据4个空间到15范围0 ~ 15总大小为16。S3结构体是三个成员841大小最大是double大小为8此时总结构体大小16刚好为8的2倍符合条件。
[] 包含S3的结构体
struct S4
{char c1;struct S3 s3;double d;
};int main()
{printf(%zd\n, sizeof(struct S4));return 0;
}运行结果32第一个成员C1对应到偏移量为0处大小为1s3为结构体s3的大小为16根据第四条规则【如果嵌套了结构体的情况嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数含嵌套结构体中成员的对齐数的整数倍。】也就是说结构体s3最大对齐数为double的8用8对齐到S4中整数倍1,2,3,4,5,6,7都不是8的整数倍跳过当偏移量为8时为对齐数8的整数倍时然后结构体整体大小为16占据范围为8 ~ 23接下来就是第三个元素d大小为8偏移量24就是8的整数倍占据了24 ~ 31,所有成员都完成了偏移量范围在0 ~ 31总大小就是32。答案就是32.看到这里的你给自己鼓个掌继续加油。 宏offsetof计算偏移量
宏offsetof可以用来计算结构体成员相对于结构体起始位置的偏移量。 宏offsetof原型
offsetof(type, member)
type是结构体类型
member是结构体中的成员。注意使用offsetof宏计算结构体成员偏移量时需要包含stddef.h头文件
# define _CRT_SECURE_NO_WARNINGS 1
#include stdio.h
#include string.h
#include stddef.h
struct S1
{char c1;char c2;int i;
};struct S2
{char c1;int i;char c2;
};struct S3
{double d;char c;int i;
};struct S4
{char c1;struct S3 s3;double d;
};int main()
{struct S1 s1 {0};//8struct S2 s2 { 0 };//12printf(结构体大小\n);printf(S1%zd\n, sizeof(struct S1));//8printf(S2%zd\n, sizeof(struct S2));//12printf(S3%zd\n, sizeof(struct S3));//16printf(S4%zd\n, sizeof(struct S4));//32printf(\n); printf(结构体S1成员的偏移量\n);printf(c1%zd\n, offsetof(struct S1, c1));//0printf(c2%zd\n, offsetof(struct S1, c2));//1printf( i%zd\n, offsetof(struct S1, i));//8printf(\n);printf(结构体S2成员的偏移量\n);printf(c1%zd\n, offsetof(struct S2, c1));//0printf( i%zd\n, offsetof(struct S2, i));//4printf(c2%zd\n, offsetof(struct S2, c2));//8printf(\n);printf(结构体S4成员的偏移量\n);printf(c1%zd\n, offsetof(struct S4, c1));//0printf(s3%zd\n, offsetof(struct S4, s3));//8printf(d%zd\n, offsetof(struct S4, d));//24return 0;
}运行图对比
为什么存在内存对⻬?
平台原因 (移植原因) 不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常。性能原因 数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于为了访问未对⻬的内存处理器需要作两次内存访问⽽对⻬的内存访问仅需要⼀次访问。 假设⼀个处理器总是从内存中取8个字节则地址必须是8的倍数。如果数据没有对齐CPU需要额外的时间来处理非对齐的内存访问这会降低性能。 总结一句话来说 结构体的内存对⻬是拿空间来换取时间的做法。
在设计结构体时既要满足内存对齐要求又要考虑节省空间可以采取以下方法
尽量将较小类型如char、short等成员放在结构体开始位置。这可以减少由对齐产生的内存浪费。 例如前面的S1和S2就很典型
struct S1
{char c1;int i;char c2;
};
struct S2
{char c1;char c2;int i;
};阿森把宝图解
修改默认对⻬数 #pragma 这个预处理指令可以改变编译器的默认对⻬数。 #pragma 原型
#pragma pack(push, 1) // 将结构体对齐数设置为1字节
struct S1
{char a; int b;
};
#pragma pack(pop)// 恢复之前的对齐数pack(push, 1)表示将当前对齐数压入栈并设置新的对齐数为1字节pack(pop)表示从栈中弹出之前的对齐数恢复默认对齐数
可以直接指定对齐数
#pragma pack(1)
struct S1
{ // 成员对齐数为1字节char a; int b;
};#pragma pack() // 恢复默认对齐数例子
#pragma pack(1)
struct S1
{char c1;char c2;int i;
};
#pragma pack()int main()
{printf(%d\n, sizeof(struct S1));return 0;
}输出 图解对比 结构体传参
按值传递传结构体 函数形参声明为结构体实参传递结构体变量。此时在函数内对形参的修改不会影响实参。
struct St
{int x;
};void func(struct St st)
{st.x 10;
}int main()
{struct St s { 0 };func(s);//传结构体printf(%d\n, s.x);
}输出
按地址传递 函数形参定义为结构体指针实参传递结构体变量的地址。函数内对形参所指结构体的修改会影响实参。
struct St
{int x;
};void func(struct St* p)
{p-x 10;
}int main() {struct St s { 0 };func(s);printf(%d\n, s.x);
}输出
传结构体指针 实参直接传结构体指针
struct St
{int x;
};void func(struct St* st)
{st-x 10;
}int main()
{struct St s;struct St* p s;func(p);printf(%d\n, s.x);
}输出10 分析 传值也就是把整个结构体传过去我们知道形参是是实参的一份临时拷贝需要再创建特别大的空间来存储结构体。 无论是传结构体指针还是传结构体地址本质上都是传地址但是传地址只需要创建一个小的空间来存储地址。 选择传地址比较好一些。 原因 函数传参的时候参数是需要压栈会有时间和空间上的系统开销。 如果传递⼀个结构体对象的时候结构体过⼤参数压栈的的系统开销⽐较⼤所以会导致性能的下降。 总结 结构体传参的时候要传结构体的地址。 总结
这次阿森和你一起学习结构体的 结构体内存对齐内存对齐包含结构体的计算使用宏offsetof计算偏移量为什么存在内存对⻬? 结构体传参的本质阿森将下一节和你一起学习结构体实现位段。
感谢你的收看如果文章有错误可以指出我不胜感激让我们一起学习交流如果文章可以给你一个小小帮助可以给博主点一个小小的赞