Unsafe, as the name suggests, is unsafe. Go defines this package name as well. Let us try not to use it as much as possible. If you use it and see the name, you will think of not using it as much as possible, or be more careful. Use it.
Although this package is not safe, it also has its advantages, that is, it can bypass Go's memory safety mechanism and directly read and write memory. So sometimes because of performance needs, you will take some risks to use this package to perform memory operations. operating.
Sizeof function
Sizeof
The function can return the size of the memory occupied by a type. This size is only related to the type and has nothing to do with the size of the variable storage corresponding to the type. For example, the bool type occupies one byte, and int8 also occupies one byte.
func main() {
fmt.Println(unsafe.Sizeof(true))
fmt.Println(unsafe.Sizeof(int8(0)))
fmt.Println(unsafe.Sizeof(int16(10)))
fmt.Println(unsafe.Sizeof(int32(10000000)))
fmt.Println(unsafe.Sizeof(int64(10000000000000)))
fmt.Println(unsafe.Sizeof(int(10000000000000000)))
}
For integer types, the number of bytes occupied means the size of the range of numbers stored by this type. For example, int8 occupies one byte, which is 8bit, so the size range it can store is -128~~127, which is −2 ^(n-1) to 2^(n-1)-1, n means bit, int8 means 8bit, int16 means 16bit, and so on.
For the int type related to the platform, this depends on whether the platform is 32-bit or 64-bit, and the largest is taken. For example, when I test it myself, the above output will find that the sizes of int and int64 are the same, because my computer is a 64-bit platform.
func Sizeof(x ArbitraryType) uintptr
The above is Sizeof
the function definition, which receives a ArbitraryType
type of parameter and returns a uintptr
type of value. ArbitraryType
Don't worry about it here , it's just a placeholder. The type is exported for documentation considerations, but it is generally not used. We only need to know that it represents any type, that is, our function can receive any type of data.
// ArbitraryType is here for the purposes of documentation only and is not actually
// part of the unsafe package. It represents the type of an arbitrary Go expression.
type ArbitraryType int
Alignof function
Alignof
Returns a type of alignment value, which can also be called alignment factor or alignment multiple. The alignment value is a value related to memory alignment. Reasonable memory alignment can improve the performance of memory read and write. For the knowledge of memory alignment, please refer to related documents, which will not be introduced here.
From the output of the above example, it can be seen that the alignment value is generally 2^n, and the maximum will not exceed 8 (for the reason, see the memory alignment rules below). Alignof
The function definition Sizeof
is basically the same. It should be noted here that the results of each person's computer may be different, similar to each other.
func Alignof(x ArbitraryType) uintptr
In addition, the function of the reflection package can also be used to obtain the alignment value, that is to say: unsafe.Alignof(x)
equivalent to reflect.TypeOf(x).Align()
.
Offsetof function
Offsetof
The function is only applicable to the offset of the field in the struct structure relative to the memory location of the structure. The offset of the first field of the structure is 0.
func main() {
var u1 user1
fmt.Println(unsafe.Offsetof(u1.b))
fmt.Println(unsafe.Offsetof(u1.i))
fmt.Println(unsafe.Offsetof(u1.j))
}
type user1 struct {
b byte
i int32
j int64
}
The offset of the field is the starting position of the field in the memory layout of the struct structure (the memory location index starts from 0). According to the offset of the field, we can locate the fields of the structure, and then we can read and write the fields of the structure, even if they are private, does it feel like a hacker. The concept of offset, we will introduce in detail in the next summary.
In addition, it is unsafe.Offsetof(u1.i)
equivalent toreflect.TypeOf(u1).Field(i).Offset
Interesting struct size
We define a struct, this struct has 3 fields, their types are byte
, int32
and int64
, but the order of these three fields can be arranged arbitrarily, so according to the order, there are a total of 6 combinations.
type user1 struct {
b byte
i int32
j int64
}
type user2 struct {
b byte
j int64
i int32
}
type user3 struct {
i int32
b byte
j int64
}
type user4 struct {
i int32
j int64
b byte
}
type user5 struct {
j int64
b byte
i int32
}
type user6 struct {
j int64
i int32
b byte
}
According to these 6 combinations, 6 structs are defined, which are user1, user2,..., user6. Now, let's guess how much memory these 6 types of struct occupy, that is unsafe.Sizeof()
the value.
You may guess 1+4+8=13, because the size of byte is 1, the size of int32 is 4, and the size of int64 is 8, and struct is actually a combination of fields, so it is normal to guess that the struct size is the sum of the field sizes.
But, however, I can clearly say that this is wrong.
Why is it wrong? Because there is memory alignment and the compiler uses memory alignment, then the final size result is different. Now we formally verify the values of these structs.
func main() {
var u1 user1
var u2 user2
var u3 user3
var u4 user4
var u5 user5
var u6 user6
fmt.Println("u1 size is ",unsafe.Sizeof(u1))
fmt.Println("u2 size is ",unsafe.Sizeof(u2))
fmt.Println("u3 size is ",unsafe.Sizeof(u3))
fmt.Println("u4 size is ",unsafe.Sizeof(u4))
fmt.Println("u5 size is ",unsafe.Sizeof(u5))
fmt.Println("u6 size is ",unsafe.Sizeof(u6))
}
As you can see from the above output, the result is:
u1 size is 16
u2 size is 24
u3 size is 16
u4 size is 24
u5 size is 16
u6 size is 16
The result came out (the result of my computer, Mac64 bit, yours may be different), 4 16 bytes, 2 24 bytes, neither the same nor the same, this shows:
- Memory alignment affects the size of struct
- The field order of struct affects the size of struct
Combining the above two points, we can know that different field orders ultimately determine the memory size of the struct, so sometimes a reasonable field order can reduce memory overhead .
Memory alignment will affect the memory footprint of the struct. Now we will analyze in detail why the different order of field definitions will cause the memory footprint of the struct to be different.
Before analyzing, let's look at the rules of memory alignment:
- For specific types, the alignment value = min (the compiler default alignment value, type size Sizeof length) . That is, between the default alignment value and the memory footprint of the type, the minimum value is the alignment value of the type. My computer defaults to 8, so the maximum value will not exceed 8.
- After each field of struct is aligned in memory, it also needs to be aligned, alignment value = min (default alignment value, maximum type length of the field) . This article is also easy to understand. Among all the fields of the struct, the smallest type between the length of the largest type and the default alignment value is taken.
The above two rules must be well understood, and only then can the following struct structure be analyzed. Here again, the alignment value is also called alignment coefficient, alignment multiple, and alignment modulus. This means that the offset of each field in memory is a multiple of the alignment value .
We know that the alignment values of byte, int32, and int64 are 1, 4, and 8, respectively, and the memory size is also 1, 4, and 8. Then for the first struct user1
, its field order is byte, int32, int64, we first use the first memory alignment rule to perform memory alignment, and its memory structure is as follows.
bxxx|iiii|jjjj|jjjj
user1
Type, the first field byte, alignment value 1, size 1, so it is placed in the first position in the memory layout.
The second field is int32, the alignment value is 4, and the size is 4, so its memory offset value must be a multiple of 4. In the current user1
one, it cannot start from the second position, and must start from the fifth position, which is the offset The shift amount is 4. Bits 2, 3, and 4 are filled by the compiler, and are generally value 0, which is also called a memory hole. So the 5th to 8th bits are the second field i.
In the third field, the alignment value is 8, and the size is also 8. Because the user1
first two fields have been ranked to the 8th position, the offset of the next one is exactly 8, which is a multiple of the alignment value of the third field. Without padding, you can arrange the third field directly, that is, from the 9th Bit to the 16th bit is the third field j.
Now that the memory length is 16 bytes after the first memory alignment rule, we start to use the second memory alignment rule for alignment. According to the second rule, the default alignment value is 8, and the maximum type length in the field is also 8, so the alignment value bit of the structure is calculated. Our current memory length is 16, which is a multiple of 8, and alignment has been achieved.
So so far, user1
the memory footprint of the structure is 16 bytes.
Now we analyze another user2
type, its size is 24, but the order of the fields i and j is changed, it takes up 8 bytes, let's see why? Or first use our memory first rule analysis.
bxxx|xxxx|jjjj|jjjj|iiii
According to the alignment value and the size it occupies, the first field b offset is 0, occupies 1 byte, and is placed in the first position.
The second field j is int64, the alignment value and size are both 8, so start from offset 8, that is, the 9th to 16th bits are j, which means that the 2nd to 8th bits are filled by the compiler .
At present, the entire memory layout has been shifted by 16 bits, which is exactly a multiple of 4 of the alignment value of the third field i, so it can be arranged directly without filling, and the 17th to 20th bits are i.
Now that all the fields are aligned, the entire memory size is 1+7+8+4=20 bytes. We start to use the second rule of memory alignment, which is the alignment of the structure, through the default alignment value and the largest field Size, find the alignment value of the structure to 8.
Now our entire memory layout size is 20, not a multiple of 8, so we need to fill the memory to make up to a multiple of 8, the smallest is 24, so the entire memory layout after alignment is
bxxx|xxxx|jjjj|jjjj|iiii|xxxx
So this is why we finally got user2
a size of 24. Based on the above method, we can get the memory layout of several other structs.
user3
iiii|bxxx|jjjj|jjjj
user4
iiii|xxxx|jjjj|jjjj|bxxx|xxxx
user5
jjjj|jjjj|bxxx|iiii
user6
jjjj|jjjj|iiii|bxxx
The answer is given above, you can refer to user1
and user2
try it when you push it. In the next article, we will introduce the operation of memory through unsafe.Pointer and the reading and writing of memory