数据字节类型的长度与结构体字节对齐

一、数据字节类型的长度

在32位机器和64机器中int类型都占用4个字节。编译器可以根据自身硬件来选择合适的大小,但是需要满足约束:short和int型至少为16位,long型至少为32位,并且short型长度不能超过int型,而int型不能超过long型。
这即是说各个类型的变量长度是由编译器来决定的,而当前主流的编译器中一般是32位机器和64位机器中int型都是4个字节(例如,GCC)。
下面列举在GCC编译器下32位机器和64位机器各个类型变量所占字节数:

C类型32位机器 (字节)64位机器(字节)
char11
short22
int44
long int48
long long88
char*48
float44
double88

总体而言,最大的不同点就是在long型和指针类型长度不一样,对于指针而言,64位机器可以寻址2^64,每个内存地址长度为64位,即8字节。

#include <string.h>
#include <stdio.h>

int main()
{
    printf("sizeof( char): %d\t", sizeof(char));
    printf("sizeof( float): %d\n", sizeof(float));
    printf("sizeof( short int): %d\t", sizeof(short int));
    printf("sizeof( int): %d\n", sizeof(int));
    printf("sizeof( long int): %d\t", sizeof(long int));
    printf("sizeof( long long int): %d\n", sizeof(long long int));
    printf("sizeof( size_t): %d\t", sizeof(size_t));
    printf("sizeof( void*): %d\n\n", sizeof(void *));

    return 0;
}

运行结果:
在这里插入图片描述

二、结构体字节对齐

1. 结构体字节对齐概念

在用sizeof运算符求算某结构体所占空间时,并不是简单地将结构体中所有元素各自占的空间相加,这里涉及到内存字节对齐的问题。从理论上讲,对于任何 变量的访问都可以从任何地址开始访问,但是事实上不是如此,实际上访问特定类型的变量只能在特定的地址访问,这就需要各个变量在空间上按一定的规则排列, 而不是简单地顺序排列,这就是内存对齐。

在C语言中,结构是一种复合数据类型,其构成元素既可以是基本数据类型(如int、long、float等)的变量,也可以是一些复合数据类型(如数组、结构、联合等)的数据单元。在结构中,编译器为结构的每个成员按其自然边界(alignment)分配空间。各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个结构的地址相同。
为了使CPU能够对变量进行快速的访问,变量的起始地址应该具有某些特性,即所谓的”对齐”。比如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除。

2. 内存字节对齐的作用及原因
  • 各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
  • 字节对齐的作用不仅是便于cpu快速访问,同时合理的利用字节对齐可以有效地节省存储空间。
  • 对于32位机来说,4字节对齐能够使cpu访问速度提高,比如说一个long类型的变量,如果跨越了4字节边界存储,那么cpu要读取两次,这样效率就低了。但是在32位机中使用1字节或者2字节对齐,反而会使变量访问速度降低。所以这要考虑处理器类型,另外还得考虑编译器的类型。在vc中默认是4字节对齐的,GNU gcc 也是默认4字节对齐。
对齐原因的举例

因为在32位操作系统(虽然64位操作系统,但是为了保证兼容性,编程仍然主要考量32位)中,数据总线是32位,地址总线是32位。
地址总线是32位,意味着寻址空间是按4递增的;数据总线32位意味着一次可读写4byte。

struct stu1  
{   
   char c1;   
   int i;  
   char c2;  
}  

在这里插入图片描述
在这里插入图片描述
不对齐就意味着,当我们执行stu1.i 时,需要读取内存两次。
而对齐后,就只需要读取一次,众所周知,I/O操作是很耗时的,编译器做出对齐的选择也就好理解。

为什么不完全按照4字节对齐的?
struct stu2  
{   
   char c;   
   short s;
   int i; 
   double d; 
}

在这里插入图片描述
在这里插入图片描述
为什么编译器采用B方案,而不采用A方案?
还是因为32数据线一次读取4个字节,
采用方案A,读取stu2.c,或者stu2.s,要一次读取4个byte,再舍弃无关内存的数据。
采用方案B,读取stu2.c,或者stu2.s,也是要一次读取4个byte,再舍弃无关内存中的数据。
同样的I/O操作,相比之下,明显方案B更节省内存。
补充: 如上图中,所示8字节的数据类型,比如double, long long,必须要读取两次内存。
明白这两点,再回看上面结构体大小的计算方法,就简单多了,也不用死记烂背了。
另外,写代码时也知道怎样节省内存了(虽然大多数时间不用考虑这点)。

3. 案例详解
#include <string.h>
#include <stdio.h>

//char占1个字节,int占4个字节,字节对齐后,使用sizeof结构体共占8个字节
struct STUDENT1
{
    char a;
    int b;
}data1;
//char占一个字节,int占4个字节,字节对齐后,使用sizeof结构体共占8个字节
struct STUDENT2
{
    char a;
    char b;
    int c;
}data2;
//char占1个字节,int占4个字节,字节对齐后,使用sizeof结构体共占12个字节
struct STUDENT3
{
    char a;
    char b;
    char c;
    char d;
    char e;
    int f;
}data3;
//char占1个字节,int占4个字节,字节对齐后,使用sizeof结构体共占12个字节
struct STUDENT4
{
    char a;
    int b;
    char c;
}data4;
//char占1个字节,int占4个字节,float占4个字节,字节对齐后,使用sizeof结构体共占24个字节
struct STUDENT5
{
    char name[10];
    int age;
    char sex;
    float score;
}data5;

int main()
{

    printf("sizeof( struct1): %d\n",sizeof(data1));
    printf("sizeof( struct2): %d\n",sizeof(data2));
    printf("sizeof( struct3): %d\n",sizeof(data3));
    printf("sizeof( struct4): %d\n",sizeof(data4));
    printf("sizeof( struct5): %d\n",sizeof(data5));
    return 0;
}

在这里插入图片描述

struct STUDENT1
{
    char a;
    int b;
}data;

我们看到 data 不是占 5 字节,而是占 8 字节。变量 a 的地址是从 00427E68 到 00427E6B,占 4字 节;变量 b 的地址是从 00427E6C 到 00427E6F,也占 4 字节。b 占 4 字节我们能理解,但 a 是 char 型,char 型不是占 1 字节吗,这里为什么占 4 字节?其实不是它占了 4 字节,它占的还是 1 字节,只不过结构体中有一个字节对齐的概念。

什么叫字节对齐?我们知道结构体是一种构造数据类型,里面可以有不同数据类型的成员。在这些成员中,不同的数据类型所占的内存空间是不同的。那么系统是怎么给结构体变量的成员分配内存的呢?或者说这些成员在内存中是如何存储的呢?通过上面这个例子我们知道肯定不是顺序存储的。

那么到底是怎么存储的呢?就是按字节对齐的方式存储的!即以结构体成员中占内存最多的数据类型所占的字节数为标准,所有的成员在分配内存时都要与这个长度对齐。我们举一个例子:我们以上面这个程序为例,结构体变量 data 的成员中占内存最多的数据类型是 int 型,其占 4 字节的内存空间,那么所有成员在分配内存时都要与 4 字节的长度对齐。也就是说,虽然 char 只占 1 字节,但是为了与 4 字节的长度对齐,它后面的 3 字节都会空着,即:
在这里插入图片描述
所谓空着其实也不是里面真的什么都没有,它就同定义了一个变量但没有初始化一样,里面是一个很小的、负的填充字。为了便于表达,我们就暂且称之为空好了。

struct STUDENT2
{
    char a;
    char b;
    int c;
}data;

那么这三个成员是怎么对齐的?a 和 b 后面都是空 3 字节吗?不是!如果没有 b,那么 a 后面就空 3 字节,有了 b 则 b 就接着 a 后面填充。即:
在这里插入图片描述
这时我们发现一个问题:所有成员在分配内存的时候都与 4 字节的长度对齐,多个 char 类型时是依次往后填充,但是 char 型后面的 int 型为什么不紧接着后面填充?为什么要另起一行?也就是说,到底什么时候是接在后面填充,什么时候是另起一行填充?
我们说,所有的成员在分配内存时都要与所有成员中占内存最多的数据类型所占内存空间的字节数对齐。假如这个字节数为 N,那么对齐的原则是:理论上所有成员在分配内存时都是紧接在前一个变量后面依次填充的,但是如果是“以 N 对齐”为原则,那么,如果一行中剩下的空间不足以填充某成员变量,即剩下的空间小于某成员变量的数据类型所占的字节数,则该成员变量在分配内存时另起一行分配。

struct STUDENT3
{
    char a;
    char b;
    char c;
    char d;
    char e;
    int f;
}data3;

首先最长的数据类型占 4 字节,所以是以 4 对齐。然后 a 占 1 字节,b 接在 a 后面占 1 字节,c 接在 b 后面占 1 字节,d 接在 c 后面占 1 字节,此时满 4 字节了,e 再来就要另起一行。f 想紧接着 e 后面分配,但 e 后面还剩 3 字节,小于 int 类型的 4 字节,所以 f 另起一行。即该结构体变量分配内存时如下:
在这里插入图片描述

struct STUDENT4
{
    char a;
    int b;
    char c;
}data4;

即将原来第二个和第三个声明交换了位置,大家看看现在 data 变量占多少字节?没错,是 12 字节。首先最长类型所占字节数为 4,所以是以 4 对齐。分配内存的时候 a 占 1 字节,然后 b 想紧接着 a 后面存储,但 a 后面还剩 3 字节,小于 b 的 4 字节,所以 b 另起一行分配。然后 c 想紧接着 b 后面分配,但是 b 后面没空了,所以 c 另起一行分配。所以总共 12 字节。内存分配图如下所示:
在这里插入图片描述

struct STUDENT5
{
    char name[10];
    int age;
    char sex;
    float score;
}data5;

“上面这个结构体变量 data 中有成员 char name[10],长度最长,是 10,那是不是要以 10 对齐?”不是,char a[10] 的本质是 10 个 char 变量,所以就把它当成 10 个 char 变量看就行了。所以结构体变量 data 中成员最长类型占 4 字节,还是以 4 对齐。该结构体变量分配内存时情况如下:
在这里插入图片描述

4. 字节对齐设置
更改C编译器的缺省字节对齐方式

在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。一般地,可以通过下面的方法来改变缺省的对界条件:

  • 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
  • 使用伪指令#pragma pack (),取消自定义字节对齐方式。

另外,还有如下的一种方式:

  • __attribute((aligned (n))),让所作用的结构成员对齐在n字节自然边界上。如果结构中有成员的长度大于n,则按照最大成员的长度来对齐。
  • __ attribute__((packed)),取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
//sizeof(struct C)值是8。

//修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
char b;
int a;
short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
//sizeof(struct D)值为7。
#define GNUC_PACKED __attribute__((packed))
struct PACKED test
{
char x1;
short x2;
float x3;
char x4;
}GNUC_PACKED;
//这时候sizeof(struct test)的值仍为8。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值