sizeof 和 MemoryLayout

先扯个题外话。

最近在看 OpenGLES,碰到 glBufferData 函数对此递归懵逼。

1
func glBufferData(_ target: GLenum, _ size: GLsizeiptr, _ data: UnsafeRawPointer!, _ usage: GLenum)

第三个参数需要计算顶点数据的大小。很多教程都是用 Objective-C 去写的,因此使用 sizeof 函数很容易就计算得到。但是 Swift 则得使用 MemoryLayout 去获取。

MemoryLayout 是 Swift 3 开始推出的。主要是用来检测数据类型的内存大小。

一些常用的数据类型的检测内存大小。

1
2
3
4
5
6
MemoryLayout<String>.size // 24
MemoryLayout<Int>.size // 8
MemoryLayout<Int16>.size // 2
MemoryLayout<Int8>.size // 1
MemoryLayout<Int32>.size // 4
MemoryLayout<Int64>.size // 8

这里有个小疑问,用 MemoryLayout<NSString>.size 得到的是 8 ,但是 Swift 中的 String 则是 24,怀疑和 String 是集合类型 有关。这里主要是涉及到机器的位数,比如 64 位机器,那么 Int 所占是 8 位,而 32 位机器则是 4 位。

另外,如果是用 Objective-C 的话,那么则是

1
2
3
sizeof(NSString); // 8
sizeof(int); // 4
sizeof(NSInteger); // 8

此外,如果是指针类型,比如 sizeof(int *) 在 32 位机器上是 4 byte 而 64 位机器则是 8 bytes。

对于数组而言,sizeof 的内部实现实际上是 sizeof(数组元素类型) 数组个数。比如 int a[10],那么 sizeof(a) 则是 40。因此,如果使用 MemoryLayout 去计算数组的话,则需要 arr.count MemoryLayout.size 这样去计算。因此可以弄成一个扩展

1
2
3
4
5
6
7
8
extension Array {
var size : Int {
return count * MemoryLayout<Element>.size
}
}
["a","b"].size // 48
[1,2].size // 16

当然有时候需要计算一些结构体或类的内存大小。

1
2
3
4
5
6
struct Dog {
let name : String // 24
let age : Int8 // 1
}
MemoryLayout<Dog>.size // 25

如果属性是可选类型的话,那么则需要加 1。比如 Dog 中的 String 是 String?,那么其所占内存大小则是 25,最终得到的 Dog 所占内存大小是 26。由此可见,使用可选类型会比较占内存。🐱🐱🐱???

1
2
3
4
5
6
struct Dog {
let age : Int8 // 1
let name : String // 24
}
MemoryLayout<Dog>.size // 32

假如将 Dog 中的 name 和 age 调转,那么得到的内存大小又是不一样的。这里主要是因为内存对齐原则。64位机器的内存对齐是 8 bytes,因此,当 age 在内存排列后是 1,需要补齐到 8 ,才能继续排列 name 。因此最终结果是 32 。

在 C 语言中,有时候用使用offsetof(a,b),来计算某个属性的偏移量。目前在 Swift 4 中并没有这样的函数去计算偏移量。在 stackoverflow 上找到的答案是建议如果 struct 用 C 语言定义,然后通过链接桥使用 offsetof 方法。另外就是,等 Swift 5 ABI 稳定。

大概就扯这些吧~

参考

What is the equivalent in Swift of offsetof(struct, member) in C?