边界对齐:从 C 到 Java

从 C 开始

边界对齐

看到下列结构体,使用 sizeof 可得该结构体大小为 24 个字节

typedef struct {
  char   a; // 1 个字节
  double c; // 8 个字节
  int    b; // 4 个字节
} AType;

C 边界对齐规则

那为什么需要对齐呢?原因也很简单,即一次读写就能把整个数给拿出来(众所周知内存的读写对于 CPU 来说开销是蛮大的)

对于 x86 对齐只是建议而不是强制,而对于 MIPS 必须强制对齐

优化结构体存储大小

由于对齐规则的存在,所以可以调整结构体内字段顺序来优化存储大小。如下面优化过的结构体大小为 16 个字节

typedef struct {
  double c; // 8 个字节
  int    b; // 4 个字节
  char   a; // 1 个字节
} BType;

此时我心里便有了一个小问号,调整字段顺序这么简单的事情应该交由编译器做,而不是交给程序员处理。

那为什么没有交由编译器做呢?

内存中按序存放

结构体中的字段在内存中是按序存放的

#include <stdio.h>

typedef struct {
  double c; // 8 个字节
  int    b; // 4 个字节
  char   a; // 1 个字节
} BType;

typedef char* ptr;

void main() {
  BType b = {1.0, 2, 'a'};
  printf("%d\n", *(p + 8)); // 输出 2
}

看回 Java

字段重新排列

public class AlignDemo {
    public static void main(String[] args) {
        AClass o = new AClass();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
    }

    public static class AClass {
        char a;
        double b;
        int c;
    }
}
AlignDemo$AClass object internals:
 OFFSET  SIZE     TYPE DESCRIPTION                               VALUE
      0     4          (object header)                           01 00 00 00 (00000001 00000000 00000000 00000000) (1)
      4     4          (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
      8     4          (object header)                           43 c1 00 f8 (01000011 11000001 00000000 11111000) (-134168253)
     12     4      int AClass.c                                  0
     16     8   double AClass.b                                  0.0
     24     2     char AClass.a                                   
     26     6          (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 6 bytes external = 6 bytes total

最终整个对象大小要补足为 8 的整数倍

可见字段顺序和类中字段顺序是不一致的,因为编译器做了优化用以压缩空间

那为什么 Java 却能这样优化呢?Java 对数据的访问做了严格的限制,指针都没有,更别提上文的骚操作了

又见 Redis

负数索引

以下代码摘自 Redis 的 sds.h

typedef char *sds;

struct sdshdr64 {
    uint64_t len;
    uint64_t alloc;
    unsigned char flags;
    char buf[];
};

static inline size_t sdsavail(const sds s) {
    unsigned char flags = s[-1]; // 这是什么骚操作?
    ...
}

因为 C 数组是没有边界检查的,所以负数索引不会报错。sds 其实是一个 char 指针,上面说到 C 结构体在内存中是按序存放的,不妨把他想象成一个数组。往前走一个 char 的宽度,便能取得 flags。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值