C语言结构体字节对齐

C语言结构体字节对齐

在学习C语言的过程中,不可能永远的只使用变量来做操作,在一些特殊的情况下需要存储更多的内容的时候,比如我们在学习c语言时的练习题,做一个仓库管理系统或者学生信息管理系统这种东西的时候,就会使用到结构体,可能在初学的时候在pc上不会去考虑过多的字节对齐的问题。因为我们的内存还是比较多,够用的。
但是工作中会尽可能的裁剪你的系统,裁剪你的硬件,做到能省就省,毕竟针对大批量的产品来说,一件产品能省下一毛钱硬件,大批量就可以节省不少成本,从而带来更多的利润。不多扯题外话,讲下字节对齐的内容,工作中可能会用到,但是面试中避免不了的。之前的另外一篇博客中有说过使用memcmp去比较结构体是否相等的问题。今天就从最简单的开始讲起。
C语言中有个sizeof关键字,其作用是用来计算得到某种数据类型所占用的内存空间大小。其用法直接上代码,直观,下文的前提是所有代码基于32位操作系统。64位同理,linux某发行版本查看系统信息:

# uname -a
Linux linux-xv9p 3.11.6-4-desktop #1 SMP PREEMPT Wed Oct 30 18:04:56 UTC 2013 (e6d4a27) i686 i686 i386 GNU/Linux
#include <stdio.h>

int main(int argc, char **argv)
{
    printf("sizeof(int) = %d\n", sizeof(int));
    printf("sizeof(char) = %d\n", sizeof(char));
    printf("sizeof(short) = %d\n", sizeof(short));
    printf("sizeof(long) = %d\n", sizeof(long));
    printf("sizeof(float) = %d\n", sizeof(float));
    printf("sizeof(double) = %d\n", sizeof(double));
    printf("sizeof(long long) = %d\n", sizeof(long long));

    return 0;
}

编译运行结果如下:
在这里插入图片描述
上面是直接针对数据的修饰类型进行求sizeof的,如果我们有一个数据类型的变量,直接求这个变量所占的内存空间也是可以的。我们修改一下代码,例如:

#include <stdio.h>

int main(int argc, char **argv)
{
/*
    printf("sizeof(int) = %d\n", sizeof(int));
    printf("sizeof(char) = %d\n", sizeof(char));
    printf("sizeof(short) = %d\n", sizeof(short));
    printf("sizeof(long) = %d\n", sizeof(long));
    printf("sizeof(float) = %d\n", sizeof(float));
    printf("sizeof(double) = %d\n", sizeof(double));
    printf("sizeof(long long) = %d\n", sizeof(long long));
*/

    int a = 5;
    int b[5] = {0};
    
    printf("sizeof(a) = %d\n", sizeof(a));
    printf("sizeof(b[5]) = %d\n", sizeof(b));

    return 0;
}

猜一下结果是多少,sizeof(a), a是int型修饰,所以sizeof(a)就等于sizeof(int),那sizeof(b)呢?b也是int型修饰,但是他是一个数组啊,数组怎么可能和同类型的一个单变量占同样大小的内存空间呢?这不合理是不是。分析一下b[5]的定义,int b[5];那b的类型是不是可以理解成int [5] b;假设这是种新类型来修饰一个变量b。合理,那sizeof(b)就等于sizeof(int*5)。来验证一下:
在这里插入图片描述
结论是成立的。这就是sizeof的用法,然后就有一种求指针的sizeof(),指针是什么。最通俗的理解就是指针是存储目标的地址的一个变量。32位机器中就是有32条总线,既然是32位,那必然是需要32bit来表示所有总线,也就是需要4byte空间大小才能表示完这32条总线,所以32位机器中所有指针的sizeof都是4,同理64位机器中所有指针的sizeof都是8。那么这32条总线能访问到的最大内存就是 0-2^32 = 4096MByte=4GByte,也就是32位操作系统最多就只能访问4G的内存,加内存也没啥用,升级操作系统吧。那2^64就不知道大到哪里去了。所以64位机器就可以疯狂加内存,只要你的主板没有限制就ok。话题岔开了,回来。
有了上面的求变量的内存空间占用,实际工作中我们还是会遇到很多结构体的处理。如下一个结构体:

struct Test
{
	int a;
	char b;
};

该结构体里面有两个成员变量,一个整型的a,另外一个字符型的b。那么sizeof(struct Test)=?那计算一下,sizeof(int)+sizeof(char) = 5。理论上这样计算是没错的。但是在内存访问的时候可能会花比较多的时间去找到这个变量,这个具体的细节暂时还没有太深入的研究,操作系统以及编译器在编译时就采取一种牺牲空间换时间的操作,去默认结构体对齐,以便于寻址更迅速,毕竟运行速度快比牺牲点内存更令人容易接受,毕竟内存一般都是有富余的。那编译器是怎么对齐的。32位机器下其实是默认按照4字节对齐的,因为总线的缘故,4字节对齐更容易寻址,64位机器对齐方式暂未研究,可能会比32位机器复杂的多。何为4字节对齐呢。如上面的结构体来说,先验证一下sizeof等于多少:

#include <stdio.h>

typedef struct Test
{
    int a;
    char b;
}test;

int main(int argc, char **argv)
{
/*
    printf("sizeof(int) = %d\n", sizeof(int));
    printf("sizeof(char) = %d\n", sizeof(char));
    printf("sizeof(short) = %d\n", sizeof(short));
    printf("sizeof(long) = %d\n", sizeof(long));
    printf("sizeof(float) = %d\n", sizeof(float));
    printf("sizeof(double) = %d\n", sizeof(double));
    printf("sizeof(long long) = %d\n", sizeof(long long));

    int a = 5;
    int b[5] = {0};

    printf("sizeof(a) = %d\n", sizeof(a));
    printf("sizeof(b[5]) = %d\n", sizeof(b));
*/
    printf("suzeof(test) = %d\n", sizeof(test));
    return 0;
}

在这里插入图片描述
所以这个结构体占用了8个字节,但是成员变量实际只占用了5个字节,那多余的三个字节去哪了?
测试一下,怎么测试呢,把这个八个字节依次取出来,看看那些是我们赋值过去的,哪些是随机的,就可以知道这个结构体的内存分布方式了。上代码,注释比较详细,不展开讲代码了:

#include <stdio.h>

typedef struct Test
{
    int a;
    char b;
}test;

int main(int argc, char **argv)
{
/*
    printf("sizeof(int) = %d\n", sizeof(int));
    printf("sizeof(char) = %d\n", sizeof(char));
    printf("sizeof(short) = %d\n", sizeof(short));
    printf("sizeof(long) = %d\n", sizeof(long));
    printf("sizeof(float) = %d\n", sizeof(float));
    printf("sizeof(double) = %d\n", sizeof(double));
    printf("sizeof(long long) = %d\n", sizeof(long long));

    int a = 5;
    int b[5] = {0};

    printf("sizeof(a) = %d\n", sizeof(a));
    printf("sizeof(b[5]) = %d\n", sizeof(b));
*/
    printf("suzeof(test) = %d\n", sizeof(test));
    char i = 0, tmp[8] = {0};
    test ts = {0x01020304,0x05};    //对结构体赋值,ts.a=0x01020304, ts.b=0x05;
    char *ptr = (char *)&ts.a;      //使用字符指针指向结构体的首地址
    for(i = 0; i < 8; i++)
    {
        tmp[i] = *ptr++;        //依次将结构体的每个字节赋值给数组tmp
    }
    for(i = 0; i < 8; i++)
    {
        printf("%d\t", tmp[i]);     //将数组的每个值打印出来,就相当于将结构体的每个字节打印出来
    }
    printf("\n");

    return 0;
}

编译运行结果:
在这里插入图片描述
所以可以看出这个结构体的成员分布了。
在这里插入图片描述
低地址存放低字节数据,同时也可以说明我这台机器是小端模式。所以在这个结构体上,char b占用了四个字节,但是有效字节只有1个,后面三个都是为了对齐填充上去的。这就是字节对齐。那如果这个结构体变了呢?

struct test
{
	int a;
	char b;
	short c;
};

会是怎样的情况呢?可以用同样的方法去测试,改下代码:

#include <stdio.h>

typedef struct Test
{
    int a;
    char b;
    short c;
}test;

int main(int argc, char **argv)
{
/*
    printf("sizeof(int) = %d\n", sizeof(int));
    printf("sizeof(char) = %d\n", sizeof(char));
    printf("sizeof(short) = %d\n", sizeof(short));
    printf("sizeof(long) = %d\n", sizeof(long));
    printf("sizeof(float) = %d\n", sizeof(float));
    printf("sizeof(double) = %d\n", sizeof(double));
    printf("sizeof(long long) = %d\n", sizeof(long long));

    int a = 5;
    int b[5] = {0};

    printf("sizeof(a) = %d\n", sizeof(a));
    printf("sizeof(b[5]) = %d\n", sizeof(b));
*/
    printf("suzeof(test) = %d\n", sizeof(test));
    char i = 0, tmp[8] = {0};
    test ts = {0x01020304, 0x05, 0x0607};    //对结构体赋值,ts.a=0x01020304, ts.b=0x05, ts.c=0x0607
    char *ptr = (char *)&ts.a;      //使用字符指针指向结构体的首地址
    for(i = 0; i < 8; i++)
    {
        tmp[i] = *ptr++;        //依次将结构体的每个字节赋值给数组tmp
    }
    for(i = 0; i < 8; i++)
    {
        printf("%d\t", tmp[i]);     //将数组的每个值打印出来,就相当于将结构体的每个字节打印出来
    }
    printf("\n");

    return 0;
}

因为我知道这个结构体还是占8个字节,所以循环这里我没该,如果自己测试没把握的情况下不要这么随意测试,会出现段错误的,编译看结果:
在这里插入图片描述
所以short是在char 后面的空余的三个字节中取了两个自己,因为本身只有两个字节,距下一个四字节对齐还空余三个字节,所以完全够用,为啥是后面的两个字节呢,因为寻址偶数更快寻址。结构图不进一步画了。
再改一下:

struct test
{
	int a;
	char b;
	int c;
};

按照猜想是不是就占用12个字节了,因为char空余的三个字节不够用的了char独占4个字节,测试:

#include <stdio.h>

typedef struct Test
{
    int a;
    char b;
    int c;
}test;

int main(int argc, char **argv)
{
/*
    printf("sizeof(int) = %d\n", sizeof(int));
    printf("sizeof(char) = %d\n", sizeof(char));
    printf("sizeof(short) = %d\n", sizeof(short));
    printf("sizeof(long) = %d\n", sizeof(long));
    printf("sizeof(float) = %d\n", sizeof(float));
    printf("sizeof(double) = %d\n", sizeof(double));
    printf("sizeof(long long) = %d\n", sizeof(long long));

    int a = 5;
    int b[5] = {0};

    printf("sizeof(a) = %d\n", sizeof(a));
    printf("sizeof(b[5]) = %d\n", sizeof(b));
*/
    printf("suzeof(test) = %d\n", sizeof(test));
    char i = 0, tmp[64] = {0};
    test ts = {0x01020304, 0x05, 0x06070809};    //对结构体赋值,ts.a=0x01020304, ts.b=0x05, ts.c=0x0607
    char *ptr = (char *)&ts.a;      //使用字符指针指向结构体的首地址
    for(i = 0; i < sizeof(test); i++)
    {
        tmp[i] = *ptr++;        //依次将结构体的每个字节赋值给数组tmp
    }
    for(i = 0; i < sizeof(test); i++)
    {
        printf("%d\t", tmp[i]);     //将数组的每个值打印出来,就相当于将结构体的每个字节打印出来
    }
    printf("\n");

    return 0;
}

编译执行:
在这里插入图片描述
结果与猜想一致。
所以以后工作中如果内存很紧张的情况下针对结构体的优化可以从字节对齐入手,一般是采用小字节成员变量在前,大字节成员变量在后的这种规则进行分配,减少内存的占用,同时减少废弃数据。以上总结,linux下结构体的字节对齐遵循与操作系统版本一致的4字节对齐方式,在数据空间足够的情况下会存在公用四字节的情况。还有更多的对齐方式不展开讲。强制对齐见另外一篇博客:https://blog.csdn.net/weixin_34153142/article/details/104759932
以上如有错误还请指出,感谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lyx_wmt

能白嫖就绝不打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值