golang USES unsafe operation not to export variables Pointer USES detail

  • 2020-06-15 09:17:43
  • OfStack

preface

unsafe. Pointer is void * like C, in golang it is a bridge for various Pointers to convert to each other. uintptr is the built-in type of golang, which is an integer that can store Pointers, while the underlying type of uintptr is int, which is interchangeable with unsafe.Pointer. The difference between uintptr and ES15en.Pointer is that unsafe.Pointer is only a general pointer type, which is used to convert Pointers of different types. While uintptr is used for pointer operation, GC does not use uintptr as pointer, that is, uintptr cannot hold objects, and the target of uintptr type will be reclaimed. golang's unsafe package is so powerful that you rarely use it. It can manipulate memory like C1, but because golang does not support direct pointer arithmetic, it is a bit cumbersome to use.

Cut to the chase. With the unsafe package, you can manipulate private variables (called "unexported variables" in golang, whose names start with a lowercase letter). Here are some examples.

Create the poit package under $GOPATH/src and create the p subpackage under poit with the following directory structure:

$GOPATH/src

----poit

--------p

------------v.go

--------main.go

Here is the code for ES56en.go:


package p

import (
 "fmt"
)

type V struct {
 i int32
 j int64
}

func (this V) PutI() {
 fmt.Printf("i=%d\n", this.i)
}

func (this V) PutJ() {
 fmt.Printf("j=%d\n", this.j)
}

The intention is clear. I want to assign values to V members i and j via the unsafe package, and then print out the output using PutI() and PutJ().

The following is main. go source code:


package main

import (
 "poit/p"
 "unsafe"
)

func main() {
 var v *p.V = new(p.V)
 var i *int32 = (*int32)(unsafe.Pointer(v))
 *i = int32(98)
 var j *int64 = (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + uintptr(unsafe.Sizeof(int32(0)))))
 *j = int64(763)
 v.PutI()
 v.PutJ()
}

There are certainly limitations, such as the need to know the member layout of the structure V, the size of the members to be modified, and the offset of the members. The core idea is that members of a structure are allocated 1 contiguous segment of memory, and the address of the first member of the structure is the address of the structure, which you can also think of as offset 0 relative to the structure. Similarly, any 1 member of the structure can be offset relative to the structure to calculate its absolute address in memory.

Let's explain the implementation of main method in detail:


var v *p.V = new(p.V)

new is a built-in method of golang that allocates 1 segment of memory (which is cleared by type of zero) and returns 1 pointer. So v is one pointer of type ES84en.V.


var i *int32 = (*int32)(unsafe.Pointer(v))

Convert the pointer v to a generic pointer and then to int32. This is where you see unsafe.Pointer in action. You cannot directly convert v to a pointer of type int32, which would result in panic. The address of v is the address of its first member, so i clearly points to v's member, i. By assigning i, you're assigning v.i, but remember that i is just a pointer, so it's worth de-referencing.


*i = int32(98)

Now I have successfully changed the value of i, the private member of v

But for ES111en.j, how do you get its address in memory? We could get the offset relative to v (unsafe.Sizeof can do that for us), but my code above doesn't do that. Don't worry, everybody. Come on.


var j *int64 = (*int64)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + uintptr(unsafe.Sizeof(int32(0)))))

We already know that v has two members, i and j, and that i comes before j by definition, while i is of type int32, meaning i takes up four bytes. So j is offset by 4 bytes relative to v. You can do this with uintptr(4) or uintptr(unsafe.Sizeof (int32(0)). The unsafe. Sizeof method is used to figure out how many bytes of space a value should take up. Note that this is not the same as C, where C is the type passed in directly and golang is the value passed in. I switched to uintptr because I needed to do pointer arithmetic. The address of v plus the offset of j relative to v gives the absolute address of ES143en.j in memory. Remember that THE type of j is int64, so now j is a pointer to ES148en.j.


*j = int64(763)

Okay, now that We have View1 cut in place, let's print:


v.PutI()
v.PutJ()

If you see the correct output, congratulations, you did it!

However, don't forget that there are some problems with the above code. Have you noticed?

Create a new w. go file in the p directory with the following code:


package p

import (
 "fmt"
 "unsafe"
)

type W struct {
 b byte
 i int32
 j int64
}

func init() {
 var w *W = new(W)
 fmt.Printf("size=%d\n", unsafe.Sizeof(*w))
}

Do I need to modify the main.go code? No, we're just testing 1. w. go defines a special method, init, which is automatically executed when importing p packages. Remember that we have imported p packages in ES172en. go. Each package can define multiple init methods that are automatically executed when the package is imported (executed before the main method is executed, usually for initialization purposes), but it is best to define only one init method in a package, otherwise you might have a hard time expecting it to behave). Let's look at the output:

[

size=16

]

Wait, it doesn't seem like it's what we thought it was. b is of type byte, accounting for 1 byte; i is of type int32, accounting for 4 bytes; j is of type int64, 8 bytes long, 1+4+8=13. What's going on here? This is because alignment has occurred. In struct, its alignment value is the maximum alignment value among its members. Each member type has its alignment value, which can be calculated using the ES193en.Alignof method, such as unsafe.Alignof (w.b) to obtain the alignment value of b in w. Similarly, we can calculate the alignment value of w. b as 1, w. i as 4, and w. j as 4. If you thought w.j had an alignment value of 8, that would be a mistake, so our previous code executed correctly (think 1, if w.j had an alignment value of 8, then the previous assignment code would have had a problem. That is, in the previous assignment, if the alignment value of v.j is 8, then there should be four bytes of padding between v.i and v.j. So getting the right alignment values is important). The alignment value is at least 1, because storage units are in bytes. So b is the first address of w and i has an alignment value of 4 and its storage address must be a multiple of 4, so there are three padding between b and i. Similarly, j needs to be aligned, but since there is no padding between i and j, Sizeof should have a value of 13+3=16. If you want to assign three private members of w via unsafe, b is the same as before, and i is assigned by skipping three bytes, which is an extra three bytes when calculating the offset. Similarly, the j offset can be obtained by simple math.

For example, unsafe can be used flexibly:


package main

import (
 "fmt"
 "unsafe"
)

func main() {
 var b []byte = []byte{'a', 'b', 'c'}
 var c *byte = &b[0]
 fmt.Println(*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(c)) + uintptr(1))))
}

With regard to padding, the FastCGI protocol is used.

conclusion


Related articles: