首页 > 系统相关 >结构体的内存对齐

结构体的内存对齐

时间:2022-10-30 21:34:50浏览次数:42  
标签:字节 int 成员 char 内存 TEST 对齐 结构

结构体对齐内存的规则是什么?如何计算一个结构体占用空间?


内存对齐规则

结构体的内存对齐,遵循以下三个原则(以下“首地址”指相对于结构体地址的偏移量):

  1. 第一个成员的首地址为0
  2. 每个成员的首地址是自身大小的整数倍
  3. 结构体的总大小,为其成员中所含最大类型的整数倍

下面以32位机器为例,通过几个例子进行说明
例1.1

#include<iostream>
using namespace std;
struct TEST{
	char a;
	char b;
	char c;
};
int main(){
	TEST test;
	cout<<(int)&(test.a) - (int)&test<<endl;
	cout<<(int)&(test.b) - (int)&test<<endl;
	cout<<(int)&(test.c) - (int)&test<<endl;
	cout<<sizeof(TEST)<<endl;
    return 0;
}

// 运行结果
// 0
// 1
// 2
// 3

image

  • a为第一个元素,首地址为0(规则1)
  • b的首地址可以是1、2、3、4,就近置于1(规则2)
  • c的首地址可以是1、2、3、4,但1为b的首地址,故置于2(规则2)
  • 因为结构体中,最大成员占1字节,故结构体TEST占3字节(规则3)

例1.2

#include<iostream>
using namespace std;
struct TEST{
	char a;
	short b;
	char c;
};
int main(){
	TEST test;
	cout<<(int)&(test.a) - (int)&test<<endl;
	cout<<(int)&(test.b) - (int)&test<<endl;
	cout<<(int)&(test.c) - (int)&test<<endl;
	cout<<sizeof(TEST)<<endl;
    return 0;
}

// 运行结果
// 0
// 2
// 4
// 6

image

  • a为第一个元素,首地址为0(规则1)
  • b占2字节,故首地址可以是2、4、6,就近置于2(规则2)
  • c占1字节,故首地址可以是4、5、6,置于4(规则2)
  • 因为结构体中,最大成员占2字节,故结构体TEST占6字节(规则3)

例1.3

#include<iostream>
using namespace std;
struct NUM{
	char b;
	double c;
};
struct TEST{
	char a[3];
	NUM num;
	int *d;
};
int main(){
	TEST test;
	cout<<(int)&(test.a) - (int)&test<<endl;
	cout<<(int)&(test.num.b) - (int)&test<<endl;
	cout<<(int)&(test.num.c) - (int)&test<<endl;
	cout<<(int)&(test.d) - (int)&test<<endl;
	cout<<sizeof(TEST)<<endl;
    return 0;
}

// 运行结果
// 0
// 8
// 16
// 24
// 32

image

  • a虽然为一个数组,但是第一个成员,因此首地址为0,其中三个元素分别按char类型填充1字节
  • 成员num为一个结构体,他的首元素b类型为char,占1字节,其中最大的成员是double类型,占8个字节。这时,结构体的首元素需要按8字节去对齐,故地址可以是8、16、24,置8
  • num.c为double类型,占8字节,按规则起始位置为16
  • 成员d为指针,32为机器上,指针占4字节,按规则其实位置之为24
  • 最后结构体的大小,需要为最大类型的整数倍,包括所含结构体中的成员,因此TEST按8字节(double)对齐,为32字节

宏申明:#pragma pack

利用宏申明,可以指定结构体的对齐方式:

  • 按n字节对齐:#pragma pack(n),n的值可以取 '1','2','4','8',或者'16'
  • 当成员所占字节大于所指定的n字节时,则其首地址须为n的倍数
  • 当成员所占字节小于所指定的n字节时,则其首地址仍是自身所占字节的整数倍
  • 当结构体中最大的元素所占字节数大于n时,则结构体的总长度须为n的倍数
  • 当n大于结构体中最大的元素所占字节数时,则指定不生效
  • 取消指定:#pragma pack(),则按默认方式对齐

例2.1

#include<iostream>
using namespace std;
#pragma pack(2)
struct TEST{
	char a;
	int b;
	short c;
};
#pragma pack()
struct TEST_1{
	char a;
	int b;
	short c;
};
#pragma pack(16)
struct TEST_2{
	char a;
	int b;
	short c;
};
int main(){
	TEST test;
	cout<<(int)&(test.a) - (int)&test<<endl;
	cout<<(int)&(test.b) - (int)&test<<endl;
	cout<<(int)&(test.c) - (int)&test<<endl;
	cout<<sizeof(TEST)<<endl;
	cout<<"-----------------------"<<endl;
	TEST_1 test_1;
	cout<<(int)&(test_1.a) - (int)&test_1<<endl;
	cout<<(int)&(test_1.b) - (int)&test_1<<endl;
	cout<<(int)&(test_1.c) - (int)&test_1<<endl;
	cout<<sizeof(TEST_1)<<endl;
	cout<<"-----------------------"<<endl;
	TEST_2 test_2;
	cout<<(int)&(test_2.a) - (int)&test_2<<endl;
	cout<<(int)&(test_2.b) - (int)&test_2<<endl;
	cout<<(int)&(test_2.c) - (int)&test_2<<endl;
	cout<<sizeof(TEST_2)<<endl;
    return 0;
}

// 运行结果
// 0
// 2
// 6
// 8
// -----------------------
// 0
// 4
// 8
// 12
// -----------------------
// 0
// 4
// 8
// 12

image

  • #pragma pack(2) 即结构体内存按2字节对齐
  • test.a为首成员,故其地址为0,占1个字节
  • test.b为int类型占4字节,大于2字节,故其首地址须为2的倍数,所以它的地址为2,占4个字节
  • test.c为short类型占2字节,其首地址须为2的倍数,所以它的地址为6,占2个字节
  • 因为TEST结构中最大成员所占的字节数为4,大于2,因此,结构体总长度须为2的倍数,故占6个字节
  • 取消指定:#pragma pack(),则按默认方式对齐,因此TEST_1占12(1+3+4+2+2)字节
  • #pragma pack(16) 指定结构体TEST_2按16字节对齐,但其中最大成员所占字节仅为4,故不生效

C++11关键字:alignas、alignof

c++11以后引入两个关键字 alignas与 alignof。其中,alignof可以计算出类型的对齐方式,alignas可以指定结构体的对齐方式。

  • alignas(n)关键字调整的是整个结构体所占内存的对齐方式,即结构体所占内存的大小必须是n的倍数
  • 最小是8字节对齐,可以是16,32,64,128
  • 当n小于结构体中最大成员所占字节数时,指定不生效

例3.1

#include<iostream>
using namespace std;

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

struct alignas(8) TEST_1{
	char a;
	int b;
	short c;
};

struct alignas(2) TEST_2{
	char a;
	int b;
	short c;
};
int main(){
	TEST test;
	cout<<"alignof(test):"<<alignof(test)<<endl;
	cout<<(int)&(test.a) - (int)&test<<endl;
	cout<<(int)&(test.b) - (int)&test<<endl;
	cout<<(int)&(test.c) - (int)&test<<endl;
	cout<<sizeof(TEST)<<endl;
	cout<<"-----------------------"<<endl;
	TEST_1 test_1;
	cout<<"alignof(test_1):"<<alignof(test_1)<<endl;
	cout<<(int)&(test_1.a) - (int)&test_1<<endl;
	cout<<(int)&(test_1.b) - (int)&test_1<<endl;
	cout<<(int)&(test_1.c) - (int)&test_1<<endl;
	cout<<sizeof(TEST_1)<<endl;
	cout<<"-----------------------"<<endl;
	TEST_2 test_2;
	cout<<"alignof(test_2):"<<alignof(test_2)<<endl;
	cout<<(int)&(test_2.a) - (int)&test_2<<endl;
	cout<<(int)&(test_2.b) - (int)&test_2<<endl;
	cout<<(int)&(test_2.c) - (int)&test_2<<endl;
	cout<<sizeof(TEST_2)<<endl;
    return 0;
}

// 运行结果
alignof(test):4
0
4
8
12
-----------------------
alignof(test_1):8
0
4
8
16
-----------------------
alignof(test_2):4
0
4
8
12

image

  • 以上三个结构体内部成员的首地址都按对齐规则排列着
  • 结构体TEST没有指定对齐方式,因此结构体总长度为4的倍数
  • 结构体TEST_1指定对齐方式为8,大于最大成员所占字节数(int,4字节),故结构体总长度应对齐成8的倍数,为16
  • 结构体TEST_2指定对齐方式为2,小于最大成员所占字节数(int,4字节),故结构体总长度仍应为4的倍数,为12,alignof检查对齐方式也为4字节对齐

有static修饰成员的结构体

  • 被static修饰的结构体成员称为静态成员。
  • 静态成员是多个结构体对象共有的成员
  • 静态成员存储在全局(静态)存储区,为多个结构体所共有,不占用结构体的大小

例4.1

#include<iostream>
using namespace std;

struct TEST{
	char a;
	static int b;
	short c;
};
int TEST::b = 1;  // 静态成员初始化 
int main(){
	TEST test;
	cout<<(int)&(test.a) - (int)&test<<endl;
	cout<<(int)&(test.b) - (int)&test<<endl;
	cout<<(int)&(test.c) - (int)&test<<endl;
	cout<<sizeof(TEST)<<endl;
    return 0;
}

// 运行结果
// 0
// -2485896
// 2
// 4

image

位域

在声明结构体时,可以指定其成员占用存储空间的二进制位数,这样的成员称为位域。

struct TEST{
	int a:3
	int b:4
};

image

TSET中定义两个位域成员a、b,他们分别占int类型8位中的3位和4位,因此结构体只需要申请一个int类型的存储空间即可容纳

  • 本质上,位域是按其类型所对应的存储单位存放的,即将位域存放在一个单元里
  • 若位数不够时,则再分配一个单元
  • 位域的长度不能超过其类型所占的长度,且必须存储在听一个存储单元里
  • 如果剩余的内存单元不能容下下一个位域则空闲不用

例5.1

#include<iostream>
using namespace std;

struct TEST{
	int a;
	char b:2;
	char c:3;
	char d;
	char e:6;
	char f:7;
	int g;
};

int main(){
	TEST test;
	cout<<(int)&(test.a) - (int)&test<<endl;
	cout<<(int)&(test.d) - (int)&test<<endl;
	cout<<(int)&(test.g) - (int)&test<<endl;
	cout<<sizeof(TEST)<<endl;
    return 0;
}

// 运行结果
// 0
// 5
// 8
// 12

image

  • 结构体TEST的首元素的地址为0,占4个字节
  • 第2、3个成员为char类型的位域,分别占2、3位,因此分配了1个char类型的存储空间,有3位为空闲位
  • 第4个成员为char类型,地址为5,占一个字节
  • 第5、6个成员为char类型的位域,分别占6、7位,需要分配2个char类型的存储空间,第一个成员占6位,空2位;第二个成员占7位,空1位
  • 第7个成员位int类型,地址为8,占4个字节
  • 结构体中,最大成员占4个字节,因此结构体的总长度应为4的倍数,所以占12个字节

共用体与结构体

共用体是一种成员共享存储空间的结构体类型。
共用体所占的内存是所有成员内存长度的最大值。

#include<iostream>
using namespace std;

struct A{
	int m;
	char a,b;
	short n;
};
union B{
	int m;
	char a,b;
	short n;
};

int main(){
	cout<<sizeof(A)<<endl;
	cout<<sizeof(B)<<endl;
    return 0;
}

// 运行结果
// 8
// 4

image

  • 结构体按规则对齐,故A所占字节为8
  • 公用体因为每个元素都共享一段内存空间,因此B所占字节数是长度最大成员的字节数,占4字节

标签:字节,int,成员,char,内存,TEST,对齐,结构
From: https://www.cnblogs.com/hiwushiyu/p/16842288.html

相关文章

  • 【数据结构-数组】数组的相关算法
    目录1无序数组的排序——快速排序1.1升序排序1.2降序排序2有序数组的查找——折半查找(二分查找)2.1升序数组的查找2.2降序数组的查找3有序数组的合并——归并思想3.1......
  • C++内存管理
    文章目录自学网站写在前面C/C++内存分布C语言内存管理C++内存管理操作内置类型操作自定义类型operatornew&operatordeletenew&delete实现原理内置类型自定义类型定位n......
  • 四、集合结构
    集合结构集合通常是由一组无序的、不能重复的元素构成和数学中的集合名次比较相似,但是数学中的集合范围更大一些,也允许集合中的元素重复在计算机中,集合通常表示的结构......
  • C语言之存储类,枚举,结构体,共用体,typedef
    目录1存储类1.1auto存储类1.2register存储类1.3static存储类1.4extern存储类2枚举2.1定义2.2操作枚举2.2.1用for循环遍历枚举3结构体3.1定义结构3.2操作结构......
  • 数据结构 玩转数据结构 4-6 使用链表实现栈
    0课程地址https://coding.imooc.com/lesson/207.html#mid=13449 1重点关注1.1使用链表实现栈代码解析见3.1  2课程内容3......
  • three.js 小车模型的基本结构
    基本结构代码import*asTHREEfrom'three'importStatfrom'three/examples/jsm/libs/stats.module'import{OrbitControls}from'three/examples/jsm/control......
  • 数据结构 玩转数据结构 6-1 为什么要研究树结构
    0课程地址https://coding.imooc.com/lesson/207.html#mid=13454 1重点关注1.1为什么研究树结构高效    2课程内容3......
  • 内存取证
    内存取证免责声明本文档仅供学习和研究使用,请勿使用文中的技术源码用于非法用途,任何人造成的任何负面影响,与本人无关.简介内存取证一般指对计算机及相关智能设备......
  • Netlink 与 struct genl_family 结构体
    一、struct 1//#include<net/genetlink.h>2#ifndef__NET_GENERIC_NETLINK_H3#define__NET_GENERIC_NETLINK_H45#include<linux/genetlink.h>......
  • 思考问题根源的四种假设法之结构性分析法
    思考社会上各种理财培训,带你赚钱的根源是否合理?“穆勒五法”并不复杂,5Why提问法用起来也十分简单,那我们是否可以用这两种方法找到所有问题的根源呢?答案是不行。为什么呢?这......