C++ 中结构体大小的计算方式

前言:
说起来C++还真是一门神奇的语言,就像你的GirlFriend一样晦涩难懂,你永远不知道下一秒她的心情会是什么样的,不过好在C++它终究还是一门编程语言,工具毕竟是死的,既然是死的那就好办了,总有方法来对付它。当你点进这篇文章或许是正带着疑惑而来的,或者曾碰到过结构体的大小飘忽不定不知道怎么计算,又或许你还没有遇到过这样的问题,不过也没关系所谓未雨绸缪也不是一件坏事,今天就来给大家讲一下C++中结构体大小的计算方式,废话不多说直接进入今天的正题:

一、C++ 中普通类型所占字节数

  • 已经是老手或者熟练掌握c++普通类型所占字节数的可以跳过这节了
  • C++中计算一个变量所占字节数的方法和 C 是一致的,都是使用 sizeof() 函数进行计算
  • 由于各个操作系统,和环境的不一样你系统中显示的类型所占空间也会和我的有所差异,本文都已博主的电脑为准。
  • 记不住? 直接简单粗暴的统统执行一遍就知道占用的空间了
# include
using namespace std;

int main(){
    cout << "char:\t\t" << sizeof(char) << endl;
    cout << "bool:\t\t" << sizeof(bool) << endl;
    cout << "short:\t\t" << sizeof(short) << endl;
    cout << "long:\t\t" << sizeof(long) << endl;
    cout << "int:\t\t" << sizeof(int) << endl; 
    cout << "float:\t\t" << sizeof(float) << endl;
    cout << "double:\t\t" << sizeof(double) << endl;
    cout << "long long:\t" << sizeof(long long) << endl;
}

输出结果

type size(字节)
char 1
bool 1
short 2
long 4
int 4
float 4
double 8
long long 8

二. C++ 中结构体大小的计算机制

  • 很多人可能都以为结构体的大小就是其内部属性的大小简单相加即可,可事实上并不是这样不妨看下面这个示例:
# include
using namespace std;

struct TestStruct  
{  
    char c;
    int i;     
};   


int main(){
    cout << sizeof(TestStruct);
}

你执行过后会发现很奇怪,输出竟然是8,按照先前的猜想应该是 char占1个字节 + int占4个字节 = 5个字节,结果却是8。但很庆幸你看到了这篇文章,我们先把这个问题放一边,看完下面的计算机制后再回过头来探讨这个问题;

  • 先来理解内存对齐的概念
    (1)为什么要有内存对齐:因为从理论上来讲,任何变量都可以从任何的地址空间上进行存和取,但是实际上并不是这样的,特定的一些类型变量只能在特定的一块内存空间中进行存取,所以就需要让这些变量按照一定的规则排列在内存空间当中,所以就不能随机的存放在内存当中,一是因为有一些平台只允许在某些特定的内存地址上访问数据,二是应为这样可以提高变量的访问速度,比如我让整形存在偶数地址空间的内存地址上面,当我访问时我就只需遍历偶数的地址空间即可。
    (2)对齐参数:这个内存对齐中一个很重要的点,每种不同的变量都有它的对齐参数,当然不同的编译环境中对齐参数会略有偏差,比如:
    Windows 32位 / VC++6.0
    char bool short long int float double long long
    长度 1 1 2 4 4 4 8 8
    对齐参数 1 1 2 4 4 4 8 8
    Linux 32位 / gcc
    char bool short long int float double long long
    长度 1 1 2 4 4 4 8 8
    对齐参数 1 1 2 4 4 4 4 4
    可以看到在32位的Linux系统下面,double和long long类型的对齐参数都变成了4,这是因为在32位Linux/gcc环境下面,如果变量类型的字节长度没有超过CPU的字长,就会取变量类型的长度作为对齐长度,如果超过了CUP的字长,则会取CUP的字长作为该变量的对齐参数,32位的Linux系统下CUP的字长为4,所以double和long long类型的对齐参数都为4,如果换成64位的操作系统就会变成8.
    (3)结构体中对齐参数的两个原则:
    原则一结构体中的变量相对于结构体的首地址的偏移量必须是变量对应的对齐参数的整数倍,这时变量对应的对齐参数会和系统默认的对齐参数进行对比取二者中较小的那个。比如 int 类型表中的对齐参数是4,它要先和系统中的默认对齐参数8进行比较,取它们两个较小的一个作为int类型变量的对齐参数,所以这里就是取4.那么此时int类型变量相较于结构体的首地址的的偏移量必须是4的整数倍如果不是整数倍就需要填充。
    原则二: 结构体总共所占有的空间必须是其对齐参数的整数倍,注意这里是说的对齐参数是结构体的对齐参数,什么是结构体的对齐参数?计算结构体的对齐参数时,会先将结构体中所有变量的对齐参数和系统默认的对齐参数进行对比,然后取二者之间小的数作为变量的对齐参数,然后结构体再在其内部所有的变量的对齐参数挑选出最大的一个,让这个数再去和系统默认的对齐参数进行比较,选两个当中小的那一个,这个数就作为了结构体的对齐参数。

二. 通过例子来理解对齐参数

  • 还是先回到最先给的那个例子来

    # include
    using namespace std;
    
    struct TestStruct  
    {  
        char c;
        int i;     
    };   
    
    
    int main(){
        cout << sizeof(TestStruct);
    }
    

    输出结果:8
    为什么会这样呢?不妨来分析一下:
    (1)首先我们先分配变量 c 的空间,因为 c 为 char 类型,它是第一个开始分配内存空间的变量,所以它现在相对于结构体的首地址的偏移量还是0,经过查表我们可以知道 char 类型对应的对齐参数为 1 而且还小于默认的对齐参数,所以对齐参数就是1,然后因为偏移量是 0 所以能被 1 整除,那么就不需要填充直接分配一个字节的空间给变量 c ;
    (2)分配完变量c后,再来分配变量 i 的空间,因为 i 为 int 类型,所以和上面同理它的对齐参数就是 4 ,但是因为前面已经分配 1 个字节的空间给了 c ,所以现在相对于结构体首地址的偏移量就是 1 ,但是 1 却不能被 4 整除,所以就需要在变量 c 后面填充 3 个空字节才能让偏移量变成 4 从而让 4 整除,这下再分配 4 个字节给变量 i,现在整个结构体占用的空间就变成了 8 个字节
    (3)最后判断结构体所占空间是否满足要求,变量 c 对应的对齐参数为 1 ,变量 i 对应的对齐参数为 4 ,那么取它们中较大的那一个就是 4 ,再把 4 跟默认对齐参数 8 进行对比,也是取小的一个那么结构体对应的对齐参数就是 4 ,再将结构体占用的空间大小 8 来除以4发现能除尽,所以就不用再填充了,结构体最终的占用空间便是 8 个字节;

  • 示例2:

    # include
    using namespace std;
    
    struct TestStruct2  
    {  
        char c;
        int i; 
        short s;    
    };   
    
    
    int main(){
        cout << sizeof(TestStruct2);
    }
    

    输出结果: 12
    解释:我们这个示例相较于前面一个示例,结构体中多了一个 short 类型的变量,那么我们接着上面的(2)小点开始讲,因为前面已经分配了 c 和 i 变量,所以现在相较于结构体首地址的偏移量就是 8 ,而且我们很容易就可以知道 short 对应的对齐参数是 2 ,现在偏移量是 8 可以被 2整除,所以不用填充,直接分配变量 s 即可,那么现在结构体总共所占的大小变成了 10,现在再来计算结构体对应的对齐参数,还是4,但是现在结构体占用的空间总数 10 不能被 4 整除,所以还需要在变量 s 后面填充 2 个字节,所以现在结构体总共占有的空间大小即是 10 + 2 = 12 个字节。

  • 示例3:

    # include
    using namespace std;
    
    struct TestStruct3  
    {  
        char c;
        int i; 
        static short s;    
    };   
    
    
    int main(){
        cout << sizeof(TestStruct3);
    }
    

    输出结果:8
    解释:这个示例和上面一个示例唯一的区别就是,short变量前面多了一个 static 关键字进行修饰,这样会使得变量 s 跑到全局初始化区的内存空间中,而存储再这个区中的变量不会被sizeof关键字检索到,所以就相当于再结构体中这个变量是’隐形‘的,就不会计算它,那么结果就回到了示例 1 的结果 ;

  • 示例4:

    # include
    using namespace std;
    
    struct TestStruct  
    {  
        char c;
        int i;     
    };   
    
    struct TestStruct4  
    {  
        int n;
        TestStruct t;   
    };
    
    int main(){
        cout << sizeof(TestStruct4);
    }
    

    输出结果:12
    解释:我们发现在这个示例中,结构体中存放着另一个结构体类型的变量,所以这里要先明确一个概念,结构体所对应的对齐参数上面已经提过了,要注意的是不要和结构体所占用的空间混淆了。这里我们可以找到结构体 TestStruct 它对应的对齐参数就是 4 我们前面已经算过了,这里来分析一下这个示例,在TestTruct4当中我们首先分配变量 n 的空间,因为它是第一个,而且 int 对应的对齐参数是 4 那么可以整除 0 ;那么就直接分配 4 个 字节给变量 n ,现在再来分配变量 t 的空间,因为前面已经分配了变量 n 所以现在的相较于首地址的偏移量为 4 ,TestStruct类型的变量 t 对应的对齐参数为 4 ,4 能整除 4 所以也是直接分配不用填充,但是TestStruct的大小为8 所以就要分配8个空间,那么TestStruct4现在已用的空间就为 4 + 8 = 12个字节了。最后再来判断是否满足法则二,也就是结构体占用的空间要为结构体所对应的对其参数的整数倍,显然TestStruct4对应的对齐参数是 4 ,满足其占用空间 12 能被 4 整除,那么就不需要填充,最后结构体TestStruct4的真实占用空间就为 12 了。

四. 补充:设置默认的对齐参数

  • 在c++ 11 后就支持了自定义对齐参数了,具体做法如下

    # include
    # pragma pack(8)
    
    struct TestStruct5  
    {  
        int n;
        double d;
    };
    
    int main(){
        cout << sizeof(TestStruct5);
    }
    

    输出结果:16

    # include
    # pragma pack(4)
    
    struct TestStruct5  
    {  
        int n;
        double d;
    };
    
    int main(){
        cout << sizeof(TestStruct5);
    }
    

    输出结果: 12

可以看到设置不同的默认值时,结果是不一样的,读者可以结合前面的推论思考,这里就不多讲了。

写在最后的话:
程序毕竟是死的,人是灵活的,出问题的地方一般来说不会是工具,基本问题都出在人,一切表面上看着玄学的东西,其内部都有规律可言,当理解了这个规律,再来回过头来看就没那么神奇了。所以学东西入门时可以囫囵吞枣掌握个大概,但是想要更上一层楼就需要沉淀,细嚼慢咽了,加油吧骚年!

你可能感兴趣的:(笔记,c++,内存计算)