内存对齐的疑惑

1,164 阅读4分钟

内存对齐的规则

内存对齐的规则有3条

struct 或 union (以下统称结构体)的数据成员:

第一个数据成员A放在偏移为 0 的地方。

然后循环下面的步骤:

  1. 假设 对齐系数为D = #pragma pack(指定的数n),在 GCC 64位下默认是8
  2. 假设 当前数据成员是 B,B 的自身长度是 b
  3. 假设 目前结构体的长度,也就是 B 的原始偏移为 Offset
  4. 检查 Offset 是否是 min(D, b) 的整数倍
    • 如果是,B 的偏移就是 Offset
    • 如果不是,就填充0,直至 Offset 是 min(D, b) 的整数倍

数据成员为结构体

如果结构体的数据成员还为结构体,则该数据成员的“自身长度”为其内部最大元素的大小。(struct a 里存有 struct b,b 里有char,int,double等元素,那 b “自身长度”为 8)

结构体的整体对齐规则

在数据成员按照上述第一步完成各自对齐之后,结构体本身也要进行对齐。对齐会将结构体的大小调整为(#pragma pack(指定的数n) 与 结构体中的最大长度的数据成员中较小那个的整数倍,不够的补齐。

内存对齐的目的

一般的解释是这样的:‘ 截屏2020-06-29 下午4.40.50.png

对此解释,我的疑惑如下:

  • 为什么不可以从地址1开始读,读取4个字节,为什么要先读取3个,再读取1个
  • 如果说是硬件实现上的原因导致必须要按照对齐系数读取块,那例子中起始地址假设成了0,如果起始地址是2呢?因为如果一个结构体A中只有两个char类型的数据成员,那它的长度就是2,对齐后也是2。假设例子中的结构体是B,如果地址0放了个A,地址2放了个B,B的对齐系数是4,那不就没有对齐了?

内存对齐对CPU效率的影响

还看到一种说法,如果对齐系数设置得偏大,内存浪费较多(这个好理解),如果对齐系数设置得偏小,会导致CPU需要访问内存的次数比理想的偏多。

比如:一个int类型是4个字节,如果对齐系数是4,就只需要访问内存1次,如果对齐系数是1,就需要访问内存4次,可是为什么不能用 起始地址+长度 的方式访问1次内存呢?

我觉得我这种想法好像又绕回到了 为什么不可以从地址1开始读,读取4个字节,为什么要先读取3个,再读取1个 这里

c语言中内存对齐的实现

c 语言中是可以指定对齐系数的,据说是通过 __attribute__ ((aligned (n))) 的方式。

struct A {
    int a;
    long b;
} __attribute__ ((aligned (1)));

但是我在在线 c 语言编译器和 MAC 下的 gcc 编译器上都看不出效果。

但是在iOS的OC文件中可以通过 #pragma pack(n) 导致当前文件中的结构体对齐系数被改变,对其他文件无效

那么问题就来了:

  • 应该C语言中确实是可以指定对齐系数的,但可能我没有找到正确的方式,或者指定对齐系数这种操纵已经被废弃了,但应该至少之前是可以的。所以,c语言编译器中是怎么实现指定对齐系数的?对齐系数总得有地方存储吧?
    • 如果对齐系数是对整个程序有效的,那对齐系数倒是好存了,但是无从考证了。。。
    • 如果对齐系数只是对某个结构体有效,那对齐系数存在哪里了?c语言的结构体没有什么猫腻,有什么没什么使用者也能看到的,从没见过结构体中有存对齐系数的地方
    • 通过其他文章中说的,对齐系数可以是1, 2, 4, 8, 16....,可以推测对齐系数应该是可以变化的,那是在一个进程中可以变化?还是每一次访问都可以不同?

如果知道对齐系数是存在哪里的,这些问题应该都会迎刃而解。

OC中内存对齐

如果定义这样一个类

@interface Student : NSObject {
    int b;
    int a;
    char c;
}

通过命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m -o object.cpp 转成c++源码,结果如下:(Student定义在ViewController中)

static struct /*_ivar_list_t*/ {
	unsigned int entsize;  // sizeof(struct _prop_t)
	unsigned int count;
	struct _ivar_t ivar_list[3];
} _OBJC_$_INSTANCE_VARIABLES_Student __attribute__ ((used, section ("__DATA,__objc_const"))) = {
	sizeof(_ivar_t),
	3,
	{{(unsigned long int *)&OBJC_IVAR_$_Student$b, "b", "i", 2, 4},
	 {(unsigned long int *)&OBJC_IVAR_$_Student$a, "a", "i", 2, 4},
	 {(unsigned long int *)&OBJC_IVAR_$_Student$c, "c", "c", 0, 1}}
};

这个结构体中最后一个数据成员是_ivar_t的数组,_ivar_t定义如下:

struct _ivar_t {
	unsigned long int *offset;  // pointer to ivar offset location
	const char *name;
	const char *type;
	unsigned int alignment;
	unsigned int  size;
};

也就是说:

int 类型的 alignment 是2,size 是4

char 类型的 alignment 是0,size 是1

what❓❓❓

完全黑人问号