栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

边界对齐:从 C 到 Java

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

边界对齐:从 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 

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

typedef char* ptr;

void main() {
  BType b = {1.0, 2, 'a'};
  printf("%dn", *(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。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1012729.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号