SoFunction
Updated on 2025-03-02

Detailed explanation of the limited operations of golang pointers

Traditionally speaking, a pointer is a value pointing to an exact memory address. This memory address can be the starting address of any data or code. There are several things in Go that represent "pointers". The most appropriate traditional meaning isuintptrType is now. This type is actually a numeric type and is also one of the built-in data types in Go language.

Depending on the computing architecture of the current computer, it can store unsigned integers of 32-bit or 64-bit, which can represent the bit pattern of any pointer, that is, the original memory address.

Go language standard libraryunsafeBag,unsafeThere is a type in the package calledPointer, also represents the bit mode of any pointer, that is, the original memory address.

Can represent any pointer to an addressable value, and it is also the aforementioned pointer value anduintptrThe bridge between values. Through it, we can perform a two-way conversion on top of these two values. Here is a very critical word—Addressable

Let's continue to sayBefore, you need to figure out the exact meaning of this word.

Which values ​​in Go are not addressable?

  • The value of a constant.
  • The literal value of the primitive type.
  • The result value of the arithmetic operation.
  • The result value of index expressions and slice expressions for various literals.There is one exception, however, that the index result value of the slice literal is addressable.
  • The result value of the index expression and slice expression for string variables.
  • The result value of the index expression for the dictionary variable.
  • Function literals and method literals, and the result value of the calling expression to them.
  • The field value of the structure literal, which is the result value of the selection expression for the structure literal.
  • The result value of a type conversion expression.
  • The result value of the type assertion expression.
  • Receives the result value of the expression.
// Example 1.	const num = 123
	//_ = &num // Constants are not addressable.	//_ = &(123) // The literal value of the base type is not addressable.	var str = "abc"
	_ = str
	//_ = &(str[0]) // The index result value of string variables is not addressable.	//_ = &(str[0:2]) // The slice result value of a string variable is not addressable.	str2 := str[0]
	_ = &str2 // But such addressing is legal.	//_ = &(123 + 456) // The result value of the arithmetic operation is not addressable.	num2 := 456
	_ = num2
	//_ = &(num + num2) // The result value of the arithmetic operation is not addressable.	//_ = &([3]int{1, 2, 3}[0]) // The index result value of the array literal is not addressable.	//_ = &([3]int{1, 2, 3}[0:2]) // The slice result value of the array literal is not addressable.	_ = &([]int{1, 2, 3}[0]) // The index result value of the slice literal is addressable.	//_ = &([]int{1, 2, 3}[0:2]) // The slice result value of the slice literal is not addressable.	//_ = &(map[int]string{1: "a"}[0]) // The index result value of the dictionary literal is not addressable.	var map1 = map[int]string{1: "a", 2: "b", 3: "c"}
	_ = map1
	//_ = &(map1[2]) // The index result value of the dictionary variable is not addressable.	//_ = &(func(x, y int) int {
	//	return x + y
	//}) // The function represented by literals is not addressable.	//_ = &() // The function represented by the identifier is not addressable.	//_ = &(("abc")) // The result value of the call to the function is not addressable.	dog := Dog{"little pig"}
	_ = dog
	//_ = &() // The function represented by the identifier is not addressable.	//_ = &(()) // The result value of the method's call is not addressable.	//_ = &(Dog{"little pig"}.name) // Fields of structure literals are not addressable.	//_ = &(interface{}(dog)) // The result value of the type conversion expression is not addressable.	dogI := interface{}(dog)
	_ = dogI
	//_ = &(dogI.(Named)) // The result value of the type assertion expression is not addressable.	named := dogI.(Named)
	_ = named
	//_ = &(named.(Dog)) // The result value of the type assertion expression is not addressable.	var chan1 = make(chan int, 1)
	chan1 <- 1
	//_ = &(<-chan1) // The result value of the received expression is not addressable。

The value of a constant will always be stored in an exact memory area, and this value is certainlyImmutable. The same is true for literal values ​​of basic types. In fact, they can be regarded as constants, but there is no identifier that represents them.

The first keyword: immutable. Since string values ​​in Go are also immutable, for a variable of string type, the result values ​​based on its index or slice are also unaddressable, because even if the memory address of this value is obtained, it cannot change anything.

The result value of arithmetic operation belongs to aTemporary results. Even if we can get its memory address before we assign this result value to any variable or constant.

The second keyword: temporary results. This keyword can be used to explain many phenomena. We can regard the evaluation results of various expressions applied to the value literal as temporary results.

We all know that there are many types of expressions in the Go language, and the commonly used ones include the following.

  • An index expression used to obtain an element.
  • A slice expression used to obtain a slice (segment).
  • Selection expression used to access a field.
  • A calling expression used to call a function or method.
  • A type conversion expression for the type used to convert values.
  • A type assertion expression used to determine the type of a value.
  • Sends an element value to or receives an element value from the channel.

We usually get a temporary result by applying these expressions to a certain value literal. For example, the index result value of array literals and dictionary literals, and for example, the slice result value of array literals and slice literals. They are both temporary results and are not addressable.

An exception to special attention is,The index result value of the slice literal is addressable. Because no matter what, each slice value will hold an underlying array, and each element value in this underlying array has an exact memory address.

So why are the slice result values ​​of slice literals not addressable? This is because the slice expression always returns a new slice value, which is a temporary result before being assigned to the variable.

If it is targeted at a variable of array type or slice type, the result values ​​of the index or slice are not temporary results and are addressable.

This is mainly becausevariableThe value of , itself is not "temporary". In comparison,Value literalThere is no end point before it has been bound to any variable (or any identifier), and we cannot refer to them in any way. Such a value is "temporary".

By applying index expressions to variables of dictionary type, the result value obtained does not belong to temporary results, but such values ​​are not addressable.The reason is that the storage location of each key-element pair in the dictionary may change, and this change cannot be perceived by the outside world.

There are always several hash buckets in the dictionary to store key-element pairs evenly. When certain conditions are met, the dictionary may change the number of hash buckets and move the key-element pairs into the corresponding new hash bucket in time. In this case, it is meaningless and unsafe to obtain a pointer to any element value in the dictionary. We don't know when the element value will be transported, nor do we know what else will be stored on the original memory address. Therefore, such values ​​should be unaddressable.

The third keyword: unsafe. "Unsecure" operations are likely to undermine the consistency of the program and cause unpredictable errors, which seriously affects the function and stability of the program.

Functions are first-class citizens in Go, so we can assign literals or identifiers representing functions or methods to a variable, pass them to or from a function. However, such functions and methods are not addressable. One reason is that functions are code and are immutable.

Another reason is that it is not safe to get a pointer to a piece of code. In addition, the result values ​​of the call to functions or methods are also unaddressable because they are both temporary results. As for the values ​​listed in the typical answer, since they are all result values ​​of some expression for the literal value of the value, they are all temporary results and are not addressable.

  1. ImmutableValues ​​are not addressable. This is true for constants, literals of value of basic types, values ​​of string variables, functions, and methods. In fact, such regulations also have safety considerations.
  2. Most are consideredTemporary resultsAll values ​​are not addressable. The result value of the arithmetic operation is a temporary result, and the expression result value for the literal value of the value is also a temporary result. But there is an exception. Although the index result value of the slice literal is also a temporary result, it is addressable.
  3. If you get a pointer to a certain value, it may destroy the consistency of the program, then it isUnsafe, this value is not addressable. Due to the internal mechanism of the dictionary, the addressing operation of the index result value of the dictionary is insecure. In addition, it is obviously not safe to obtain the address of a function or method represented by a literal or identifier.

What are the limitations for use of unaddressable values?

Of course, the address operator cannot be used&Get their pointers. However, applying addressing operations to unaddressable values ​​will cause the compiler to report an error, so don't worry too much. You just need to remember the rules I mentioned earlier and pay attention to them in advance when encoding.

func New(name string) Dog {
     return Dog{name}
}

Let's write a function for itNew. This function will accept a name callednameofstringA parameter of type and will initialize aDogA value of type, and finally returns that value. What I'm asking now is: If I call the function and call the pointer method of its result value directly in a chained waySetName, then can the expected results be achieved?

 New("little pig").SetName("monster")

becauseNewThe value of the function's call result is unaddressable, so it cannot be addressed. Therefore, the above chain call will cause the compiler to report two errors, one is the result, that is, it cannot beNew{"little pig"}The pointer method is called on the result value. One is because: it cannot be obtainedNew{"little pig"}address.

In addition, we all know that ++ and – in the Go language do not belong to operators, but are important components of self-increment statements and self-inferior statements respectively.
Although the syntax definition in the Go language specification is that as long as an expression is added to the left of ++ or –, it also defines an important limitation, that is, the result value of this expression must be addressable. This makes it impossible for expressions for value literals to be used here.

However, there is an exception to this. Although both the result values ​​of dictionary literals and dictionary variable index expressions are not addressable, such expressions can be used in self-increment and self-deductive statements.

There are two similar rules. One is that in the assignment statement, the result value of the expression on the left side of the assignment operator must be addressable, but the index result value of the dictionary is also OK.

Another is that in a for statement with a range clause, the result values ​​of the expression on the left side of the range keyword must also be addressable, but the index result values ​​of the dictionary can also be used here.

How to manipulate addressable values?

It's like* DogThe value of type such a pointer value anduintptrThe bridge between values, so how do we use itTransfers anduintptrThe underlying operation to manipulate likedogWhat about such a value?

First of all, this is a black technology. It can bypass the repeated inspections of Go compilers and other tools, and achieve the purpose of sneaking into memory to modify data. This is not a normal programming method. It is dangerous to use it and may cause safety risks.

We should always prioritize using the API provided in regular code packages to write programs, and of course we can also write programs likereflectas well asgo/astSuch a code package is used as an alternative. As a developer of upper-level applications, please use it with cautionunsafeAny program entity in the package.

dog := Dog{"little pig"}
dogP := &dog
dogPtr := uintptr((dogP))

I first declared a Dog variable dog, and then used the address operator&, take out its pointer value and assign it to the variable dogP.

Finally, I used two type conversions, firstdogPConverted into oneThe value of type, then the latter is converted into auintptrand assign it to the variabledogPtr. Behind this are some conversion rules, as follows:

  1. A pointer value (e.g.* DogValue of type) can be converted to aThe value of type, otherwise
    The same is true.
  2. oneuintptrValues ​​of types can also be converted to avalue of type and vice versa.
  3. A pointer value cannot be converted directly into auintptrThe same is true for the value of type, and the other way around.

So, for pointer values ​​anduintptrConversion between type values ​​must be usedValue of type is used as a transit. Then, we convert the pointer value touintptrDoes a value of a type have any meaning?

namePtr := dogPtr + ()
nameP := (*string)((namePtr))

Here you need toOnly by using functions together can you see the clues.The function is used to get the offset between the starting memory address of two values ​​in memory, in bytes.

One of these two values ​​is the value of a certain field, and the other is the structure value to which the field value belongs. When we call this function, we need to pass the selection expression for the field to it, for example

With this offset, there is also the starting storage address of the structure value in memory (heredogPtrvariables represent), add them together and we can getdogPofnameThe starting storage address of the field value. This address is made of variablenamePtrrepresent.

After that, we can use two more types to convertnamePtrConvert the value to a* stringThe value of type, so that you get the pointerdogPofnamePointer value of field value.

You may ask, can't I get this pointer value by just using the address expression &()? Why do you go around so much? You can imagine that if we don’t know what the structure type is and cannot get the dogP variable, then can we still access its name field?

The answer is, as long as there isnamePtrThat's fine. It is an unsigned integer, but it is also a memory address pointing to the internal data of the program. It may bring us some benefits, such as directly modifying internal data buried very deeply.

However, once we intentionally or unintentionally leak this memory address, others can change it wantonlyThe value of the , and any data stored on the surrounding memory address is already there.

The above is a detailed explanation of the limited operation of golang pointers. For more information about golang pointers, please pay attention to my other related articles!