02 - 内存对齐解析-结构体嵌套结构体

一 思考

struct Person{
   int a;
   char b;
   double c;
} person;

struct People{
    char b;
    double c;
    int a;
}people;
int main ()
{
    printf ("%d\n",sizeof(people);  
   printf ("%d\n",sizeof(person);  
    return 0;
}
各类型所占内存大小

真机运行,64位系统上 char + int + double = 1 + 4 + 8 应该占12个字节 ,但运行打印后发现为24 ,16,说明系统存储相应类型有其固定方式即为内存对齐

一 为什么要内存对齐

为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问. 例如2-5 4字节数据,在没有内存对齐的情况下要取两次 0-3 4-7 才能得到2-5所需数据.

二 规则 - 如何计算

  • 假设m为当前成员起始存储位置 n为其所需存储大小 需满足 m % n == 0
    否则需从m+1位开始 直到满足条件
  • 当结构体嵌套了结构体时,以数据成员的结构体的自身长度作为外部结构体的最大成员的内存大小,比如结构体a嵌套结构体b,b中有charintdouble等,则b的自身长度为8开始计算b结构体内子成员位置
  • 最后结构体的内存大小必须是结构体中最大成员内存大小的整数倍,不足的需要补齐

解释下上面的24 16 是如何得到的:

Person 结构结构体
计算思路
  • int a 占4个字节从头开始
  • chat b 一个字节 占据 4号位
  • double 8个字节 5, 6 , 7, 无法整除8 故偏移到8号位置 开始到15号 共占8位
  • 结构体总大小位置为16 最大成员为double c8位 可被16整除 故返回16个字节
People 结构结构体
计算思路
  • chat b 从0开始 占一个字节
  • double c8个字节偏移到8号位置向后8位到15
    -int a 4字节可以被16整除 从16开始向后4位到19
  • 最大成员为double c8位, 向后补齐到23 ,共24位可整除8,返回24个字节
三 结构体嵌套结构体
struct Teacher{
    char d;
    int e;
    short f;
    int g;
}teacher;

struct tClass{
    char a;
    int b;
    short c; 
    struct Teacher teacher1;
}class;

int main ()
{
    printf ("%d\n",sizeof(class); //28
    return 0;
}
计算过程
  • chat a , int b , short c 如上面结构体一样计算到位置9
  • Class中最大成员为int 4字节 所以偏移到12号位置存储class中的chat d
  • 剩下的类似普通结构体直到27号位置存储完共28位 28 可以整除最大成员大小 int 4字节 返回28

三 拓展, 应用

  • pragma pack(n) & attribute((aligned (n)))

#pragma pack(1)
struct Person{
  int a;
  char b;
  double c;
} person;// sizeof(person) =  13
#pragma pack()

#define PACKED __attribute__((packed))
struct  PACKED People{
   char b;
   double c;
   int a;
}people;//sizeof(people) =  13
  • #pragma pack(n)为设置采用多少字节对齐
  • #define PACKED __attribute__((packed)) 取消编译过程中的优化对齐

四 OC中的内存优化

  • 由 Person 与People 我们可以得知,在启用内存对齐后,成员相同的结构体,成员顺序不同,则最后实际开辟内存大小也不同,并且所占内存大小大的成员在结构体位置靠前时,最终开辟的空间可能会更小. 不过OC中采用的是16字节对齐,这种方式优化没意义.

OC 属性重排验证

@interface Person : NSObject

@property(nonatomic,assign)NSInteger age;

@property(nonatomic,assign)BOOL sex;

@property(nonatomic,copy)NSString *name;

@property(nonatomic,copy)NSString *address;

@end

-声明一个对象Person 其属性顺序如上

  • 赋值
   Person *person = [[Person alloc] init];
   person.name = @"范热热";
   person.address = @"li bed";
   person.age = 100;
   person.height = 400;
   person.a = 'f';
   person.b = 'b';
  
   NSLog(@"objc对象类型占用的内存大小:%lu",sizeof(person));
   NSLog(@"objc对象实际占用的内存大小:%lu",class_getInstanceSize([Person class]));
   NSLog(@"objc对象分配的内存大小:%lu",malloc_size((__bridge const void*)(person)));
  

查看打印结果 为 8 40 48

  • sizeof(person) 得到的是person指针的大小 占8位 没毛病
  • malloc_size((__bridge const void*)(person)) 实际开辟的大小,采用16字节对齐要被16整除 得48 也可以
  • class_getInstanceSize([Person class]) 这个40是如何得到呢 答案是苹果实际采用的是8字节对齐 验证一下


    person 内存分布
  • 首先我们发现 实际内存中存储的属性值与我们在.h中定义的顺序并不一样
  • 第一个占8位的为ISA指针地址
  • 第二个8位 一共包含了 int age chat a chat b三个属性
  • 第三个8位 为 int height
  • 第四个第五个8位为 NSString 类型
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容