C++标准 alignas 与 windows 封装指令 (pack pragma) 的区别

C++标准 alignas 与 windows 封装指令 (pack pragma) 的区别

结构体内存对齐二方面的影响:

1.基于语言对齐规则的影响:语言的对齐规则
参考:https://blog.csdn.net/weixin_43698578/article/details/104801421【主要影响因素是成员的对齐大小,基本类型的对齐大小就是其sizeof大小;复合类型的对齐大小是其每个成员对齐大小的最大值(若成员还是复合类型,则递归应用此规则)】
基于语言对齐规则的影响又可细分为 未显式指定alignas下的对齐规则和 显式指定alignas的对齐规则
其实独立的来看 无论是否显示指定alignas,语言的对齐规则是一样的;显示使用C++标准关键字alignas 的语义就是直接指定 复合类型的对齐大小的=>显示指定alignas只是强制对类型或者成员指定一个对齐大小;仅此而已,再无其他特别之处=>【使用显示alignas指定对齐大小只会影响到指定的成员或者类型本身的对齐大小, 不会影响到其他成员;如果对复合类型本身显示指定alignas也不会影响到复合类型自己的成员的对齐大小】【这里的无特别之处是从对齐语言规则影响对齐大小的角度看的,而如果考虑到是否压制pack的效果 是否显示指定alignas的区别就大了(见下)】
=>所以alignas这样干巴巴的直接指定是有限制的那就是不能指定比此复合类型按原生(正常方式)判定的对齐大小更小的值!

但是之所以要细分是否显示指定了alignas是因为,alignas会压制pack 指令的效果=>alignas与pack指令效果是互斥的;
但是未显示指定alignas的【即默认对齐大小的】语言对齐规则与pack效果共同作用

首先来说pack被显示aligna对齐语言规则压制的机制
结论: pack 指令的作用(即:封装对齐值与 对齐大小的作用) 仅在默认情况【即对齐大小不以alignas指定】下有效;=>【pack指令与显示指定alignas是互斥关系;pack指令在默认对齐大小上才发挥作用】
一旦一个复合类型的某一个成员指定了alignas=>pack 指令对此成员失效,对此复合类型也失效;但是对其他还是默认对齐情况的成员有效【即未显示指定alignas的成员】
一旦一个复合类型本身指定了alignas ,则pack 指令对复合类型本身失效;pack 指令 但是对复合类型的成员还是有效的

下面来说明 pack指令如何与默认对齐大小的对齐语言规则共同作用【此部分的阐述直接放在下面 “基于封装堆栈的影响” 中】

2.基于封装堆栈的影响 pack pragma 指令

平台编译器在内部会维护一个封包(封装)堆栈;此封包(装)堆栈栈顶的记录项对应的值;就是当前pack指令上下文处使用的封装对齐值【这是一个新概念】;这个封装对齐值会影响到结构体对齐的过程:
具体行为是【在pack效果没有被压制的情况下】=>将每个成员的对齐大小与此封装对齐值比较,并选用较小的【注,这里是取用较小的了】;作为对齐过程中影响此成员本身的内存布局的实际对齐值(就暂时称为对齐分布值吧);每个成员在结构体中内存偏移位置必须是其对齐分布值的整数倍;整个结构体对象在内存中的位置,也必须是这个结构体对象的对齐分布值的整数倍!(这里要想pack指令对结构体对象有效,必须结构体类型本身和其成员都没有显示指定alignas)

在pack 指令没被压制的情况下
(每个实体【无论是成员还是对象】)的对齐分布值=>是由 封装对齐值 和 此实体的默认对齐大小 共同决定【即这二个值比较并选用最小的】

封装对齐值:(编译期由编译器根据编译程序内部维护的封装堆栈栈顶记录项的值为每个编译上下文指定)=>[这里pack 指令的编译上下文有二种]
【是编译源代码时,在编译过程(对于编译程序是运行期动态的)中编译器内部维护的封装堆栈的栈顶记录项对应的值确定;】
在编译源码过程中,编译器编译时会处理二种上下文状态:【这里上下文不区分是基础,还是复合的(类型定义处或者成员定义处)=>即不管什么类型都一样,区别在于是类型定义上下文还是成员定义上下文】

一种是pack 类型定义上下文=>就是在编译期编译器运行时处理每个类型定义处时

如果此pack上下文指令没有被压制:
编译到类型定义上下文时=>会将编译期内部动态维护的封装堆栈的栈顶记录项对应的值 应用到此类型的默认对齐大小上,应用方式=> min(pack封装对齐值,类型的默认对齐大小)=>类型的默认对齐大小会引发递归】
=>得到此类型的新的默认对齐大小【仅此而已】【没错就是被当做此类型的默认对齐大小,再无其他】

一种是pack 成员定义上下文

如果此pack上下文指令没有被压制:
使用成员定义处对应的结构体或者类的pack 封装值【这对于结构体来说是其pack类型定义上下文】,然后将此封装值与成员本身的默认对齐大小【此成员本身默认的对齐大小受到成员对应的类型的pack类型定义上下文影响】比较;取最小的值作为此成员在这个结构体内的偏移单位=>min(结构体定义处pack 封装值,成员默认对齐大小)

注:这二种(类型定义,成员定义)pack上下文状态不是机械分割的,而是递归作用;
递归:
从 成员本身的pack成员定义上下文暂开视角
=> 向上 依赖与成员所属的结构体类型的pack类型定义上下文;
=>而向下又依赖于成员本身的默认对齐大小,成员本身的默认对齐大小又依赖于成员本身对应的类型的pack类型定义上下文;
至此形成递归链=>递归链最终收敛于基础类型(非复合类型)上

无论处于pack指令的何种上下文状态=>编译每个上下文状态时取封装堆栈的栈顶记录值,然后根据当前上下文状态进行递归作用的方式是不变的
【注:编译器编译每个模块时,封装堆栈的栈顶记录项有一个默认的值;vs里面可以设置这个默认值 (即结构体对齐方式)】

总结:
1.C++标准 显示指定对齐 alignas 会压制 pack 指令上下文
2.当pack 指令没有被压制时,pack 指令 上下文 对应的封装对齐值 会与 成员的默认对齐大小发送作用【形成递归链,见上】
在递归链上 pack 封装对齐值与原生默认对齐大小生成的该成员的新的默认对齐大小可以继续在此递归链上发挥作用
【原生默认对齐大小,默认对齐大小=min(封装指,默认对齐大小)】=>都可在递归链上继续与 其他pack 封装值作用【不会压制 pack指令】【仅仅只有显式指定alignas才会压制 pack 指令】

在这里插入图片描述
windows官方对pack指令的说明:
在这里插入图片描述
在这里插入图片描述
测试符合预期:
在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;
#pragma pack(2)
struct Test
{
        char c=1;
        int m = 2;
};
#pragma pack()
struct TestOri
{
        char c = 1;
        int m = 2;
};
struct alignas (8) TestStd
{
        char c = 1;
        int m = 2;
};
struct alignas (16) TestStdEx
{
        char c = 1;
        alignas (8) int m = 2;
};
#pragma pack(2)
pack 指令对成员m 和 结构体 TestStdExPro3 都失效
struct alignas (16) TestStdExPro
{
        char c = 1;
        alignas (8) int m = 2;
};
//pack 指令对 结构体类型 TestStdExPro1 失效;但是对成员起作用
struct alignas (16) TestStdExPro1
{
        char c = 1;
         int m = 2;
};
//vs2022实测 此种情况 pack 封装对齐值 对成员和结构体都起作用【与成员的对齐大小比较】
struct  TestStdExPro2
{
        char c = 1;
        int m = 2;
};
//pack 指令对成员m 和 结构体 TestStdExPro3 都作用失效;但是对成员 n pack指令有效
struct  TestStdExPro3
{
        char c = 1;
        alignas (8) int m = 2;
        __int64 n = 3;
};
#pragma pack()
//pack指令与显示指定alignas是互斥关系;pack指令在默认对齐大小上才发挥作用
//结论: pack 指令的作用(即:封装对齐值与 对齐大小的作用) 仅在默认情况【即对齐大小不以alignas指定】下有效;
//一旦一个复合类型的某一个成员指定了alignas=>pack 指令对此成员失效,对此复合类型也失效;但是对其他还是默认对齐情况的成员有效【即未显示指定alignas的成员】
//一旦一个复合类型本身指定了alignas ,则pack 指令对复合类型本身失效;pack 指令 但是对复合类型的成员还是有效的
/*-----------------------------------*/
#pragma pack(show)
#pragma pack(1)
struct WeightOri
{
        char c = 1;
        int m = 2;
};
#pragma pack()
#pragma pack(4)
struct Weight
{
        char c = 3;
        __int64 m = 4;
};
#pragma pack()
#pragma pack(2)
#pragma pack(show)
//成员r的对齐分布由上下文pack指令作用;这个作用是 在成员r定义的上下文的pack指令指定的封装对齐值,与r成员的对齐大小比较
// 而r成员因为是复合类型成员所以 又有一个递归规则在这里:
// r的类型对齐大小【在r的类型上下文处】是 先按照语言对齐规则得到r对应的类型的对齐大小【这只是一个中间值】,然后如果在此上下文处pack指令没被压制;则应用此处上下文的pack作用=>最终r对应的类型的对齐大小是取min(封装对齐值,默认(非显示)对齐大小)
// 在r成员定义上下文处 的pack指令又可以与 根据上面得到的 r对应的类型的对齐大小 共同作用 【默认对齐大小,pack指令与默认对齐大小得到新默认对齐大小都不会压制pack指令】【递归规则在此处】
//但是作用不会延深到r的成员中;r的成员的对齐分布由r的成员所在上下文的pack指令作用
struct Cat
{
        char c = 1;
        WeightOri r;
        Weight t;
        
};
#pragma pack()
int main()
{
        constexpr auto rt = sizeof(Weight);
        Weight vieww;
        constexpr auto rtcat = sizeof(Cat);
        Cat viewcat;
        /*--------------------------------------------*/
        constexpr auto res = sizeof(Test);
        constexpr auto res2 = sizeof(TestOri);
        constexpr auto res3 = sizeof(TestStd);
        constexpr auto res3expro = sizeof(TestStdEx);
        TestStdEx view;
        constexpr auto res3ex = sizeof(TestStdExPro);
        TestStdExPro viewpro;
        constexpr auto res3expro1 = sizeof(TestStdExPro1);
        TestStdExPro1 viewpro1;
        constexpr auto res3expro2 = sizeof(TestStdExPro2);
        TestStdExPro2 viewpro2;
        constexpr auto res3expro3 = sizeof(TestStdExPro3);
        TestStdExPro3 viewpro3;
        return 0;
}
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值