结构体字节大小与位域

本文探讨了C++和C语言中结构体的大小计算,深入讲解了字节对齐的原理和作用,包括结构体内存计算、自定义字节对齐策略。此外,还介绍了位域的概念,强调了位域使用时的最佳实践,如采用无符号类型并保持长度一致。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、结构体大小

首先我们先定义一个非空结构体,通过sizeof()命令观察其内存字节大小,发现一个结构体的大小并不是结构体内各数据类型大小简单的相加。那么56究竟是怎么来的呢?这就引入了结构体对齐方式的问题。

#include<stdio.h>
struct Stu{
    char name[20];//20
    unsigned int age;//4
    char tel[15];//15
    float scores[3];//12
    char sex;//1
};//简单相加后为52字节
int main(){
    struct Stu stu;
    printf("%u",sizeof(stu));//实际为56字节
    return 0;
}

二、结构体内存计算

  • 在这之前,我们需要知道什么叫字节对齐?

    ​ 字节对齐是指数据类型按照固定的字节大小排列,方便CPU和内存的读取。

    ​ 而结构体内部的数据类型并不是都相等的,这个时候就需要字节对齐来提高计算机的读取效率。

  • 结构体的默认存储方式是以结构体内部最大基本数据类型所占字节数进行对齐。

  • 所以我们可以知道结构体所占内存大小为最大单成员类型的整数倍。

    拿上面的结构体来说明:

    struct Stu{
        char name[20];//20
        unsigned int age;//4
        char tel[15];//15
        float scores[3];//12
        char sex;//1
    };
    

    上面的变量中最大的数据类型是intfloat,都是4个字节

    灰色部分为未被使用的字节空间,所以我们容易知道结构体实际所占字节大小为56字节。

    注意:所有数据类型的大小在内存中存储的地址一定是它的类型的倍数。

    由上图可知,如果要进行优化,可把第三行的char放到第二行未被使用的一字节,即char sexfloat scores[3]交换位置,节省了一个字节的空间,所以优化后字节为54。

    一般来说按照数据类型所占字节从大到小的方式排列结构体内部元素来最大节省空间。

    但是这种情况以double或float在前面的情况下可能不利于阅读,比如正常下是先char姓名后float分数,若按上面规则为先分数后姓名。

三、为什么需要字节对齐

我们对比对齐访问与不对齐访问,对齐访问牺牲了内存空间以换取速度性能,而非对齐访问牺牲访问速度性能以换取对内存空间的完全利用。

说的明白点,就是为了访问结构体成员效率更高,能够更高效的读取数据,虽然会牺牲一点点内存。

四、自定义字节对齐

通过对齐命令

#pragma pack(n):以n字节对齐

#pragma pack():取消指定对齐,恢复缺省对齐

若是#pragma pack(1)即以1字节对齐,不存在未被使用的字节空间。

Alt

五、位域

  • 定义:将一个字节分为几个段,每一段表示一个对象,这样一个字节就可以表示多个对象。
struct BF
{
    int a:8;//位域长度不能超过int的长度32bit
    int b:2;
    int c:6;
};
  • 位域的存储

    当使用有符号类型来定义位域,并且无意中使用到了正负(有意或者无意)特性时,就会出现问题。

    struct BitField{
        char a : 2;
        char b : 3;
        char c : 3;
    };
    int main(){ 
    struct BitField BF;    
    // 位域赋值
    BF.a = 03;   // 11
    BF.b = 05;   // 101
    BF.c = 02;   // 010    
    printf("%d,%d,%d\n", BF.a, BF.b, BF.c);
     
    // OUTPUT:  -1(0xff, 1111 1111), -3(0xfd, 1111 1101), 2(0x02, 0000 0110)
    // 可见,当为域的最高位是1的时候,会进行符号扩展,而且这也取决于编译器的实现
    // 因此,为避免此类问题,最好使用无符号类型定义位域 
    // 如果把BitField中的char换成unsigned char就没有问题了,输出是3, 5, 2
    }
    

    从其内存布局可以看出,使用位域的最佳实践是:

    第一,位域的类型要使用无符号类型,并且在整个结构体内部要保持一致;

    第二,位域的总长度尽量与类型的长度保持一致;

    如下代码所示:

    struct BitFieldDemo
    {
        unsigned char a : 1;
        unsigned char b : 3;
        unsigned char c : 4;
    };
    
结构体字节对齐引起的访问越界问题通常出现在以下情况下: 1. 结构体中存在字节对齐的成员变量,访问该成员变量时,可能会访问到结构体之外的内存空间。例如,如果某个成员变量的长度是4字节,但该结构体字节对齐方式是8字节,那么访问该成员变量时,可能会访问到结构体之外的4字节内存空间,从而导致访问越界。 2. 结构体中存在数组类型的成员变量,访问该数组时,可能会访问到数组之外的内存空间。例如,如果一个数组的长度是10,但该结构体字节对齐方式是16字节,那么访问该数组时,可能会访问到数组之外的6字节内存空间,从而导致访问越界。 为了避免结构体字节对齐引起的访问越界问题,可以采取以下措施: 1. 使用编译器提供的或自定义的对齐方式,以确保结构体中成员变量的对齐方式符合要求。 2. 避免在结构体中使用数组类型的成员变量,或者使用动态内存分配等方法来管理数组,以确保数组访问不会越界。 3. 避免在结构体中使用类型的成员变量,或者使用运算等方法来确保访问不会越界。 4. 在编写代码时,注意检查结构体中的成员变量访问是否越界,可以使用断言等方法来检查访问边界。 总之,结构体字节对齐引起的访问越界问题需要引起重视,需要在编写代码时注意结构体成员变量的对齐方式和访问边界,以确保程序的正确性和稳定性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值