Freedom is what you do with what’s been done to you.
— Jean-Paul Sartre
Medium Link: Pointers You Should Know in Golang | by Wesley Wei | Jul, 2024 | Programmer’s Career
Hello, here is Wesley, Today’s article is about Pointers in Go. Without further ado, let’s get started.💪
1.1 Pointer Basics
Introduce what a pointer is, how it works in memory, and why we need pointers.
In the Go language, if we want to store an integer, we use the int
type, and if we want to store a string, we use the string
type. But how do we store a memory address? What data type should we use?
The answer is a pointer.
When we declare a variable, the computer allocates a block of memory for it and stores the data in that block. A pointer is a variable that stores the memory address of another variable. Through a pointer, we can indirectly access or modify the value stored at that memory address.
For example:
1 | var a int = 42 |
In this code, a
is an integer variable, and p
is a pointer that points to a
. The &a
operator represents the memory address of a
, and p
stores that address.
As shown in the diagram, a pointer type variable itself has its own memory address.
We all know that using pointers has several benefits, including:
- Efficient data transfer: When calling a function, passing a pointer instead of the entire variable can avoid copying data and improve program performance.
- Sharing data: Multiple functions or data structures can share the same data by operating on the same memory address through pointers, achieving data sharing and consistency.
- Dynamic memory management: Through pointers, we can dynamically allocate, access, and release memory to meet more complex memory operation requirements.
Of course, Go’s pointers also have their own special features, which we will continue to explore below.
1.2 Pointer Declaration and Usage:
Learn how to declare and initialize pointers in the Go language, and understand how to use them.
In the Go language, declaring and initializing pointers is very simple. You can use the *
symbol to declare a pointer type and the &
symbol to get the address of a variable and initialize a pointer.
1 | package main |
In this example, we first declare an integer variable a
, then declare a pointer that points to a
. Through *p
, we can access and modify the value of a
.
In Go, the zero value of a pointer is nil
, indicating that the pointer does not point to any valid address. Before using a pointer, it’s usually necessary to check if the pointer is nil
to avoid runtime errors.
1 | var p *int |
More information about nil
: Nil Notions You Should Know in Golang | by Wesley Wei | Jun, 2024 | Programmer’s Career
1.3 No Support for Pointer Operations
Go language does not support direct pointer operations.
In C language, you can freely use pointers (Pointer
) to operate memory, and C language supports pointer operations, which can develop high-performance programs, but it also easily causes memory leaks and overflows.
Many syntax and programming ideas in the Go language come from the C language, but the Go language emphasizes safety and simplicity. To avoid programming errors and complexity, the Go language does not support direct pointer operations. For example, you cannot increment or decrement a pointer to traverse memory addresses.
1 | var a int = 42 |
In summary, to enjoy the benefits of pointers while avoiding their dangers, Go language restricts pointer operations as follows:
- Pointers cannot perform mathematical operations.
- Pointers of different types cannot be converted to each other.
- Pointers of different types cannot be compared.
- Pointers of different types cannot be assigned to each other.
However, in some scenarios, using non-safe pointers is more convenient and efficient, so the unsafe
package provides an unsafe.Pointer
type, which can be referred to in section 1.9.
1.4 Pointer Parameters in Functions:
Learn how to use pointer parameters in functions to modify external variable values within the function.
In Go language, function parameters are passed by value. This means that when a variable is passed to a function, the function receives a copy of the variable, rather than the original variable itself. Modifying the copy will not affect the original variable.
If you want to modify an external variable’s value within a function, you can pass a pointer to achieve this.
1 | func safeUpdate(ptr *int) { |
The advantages of pointer parameters:
- Reduce memory copying: Passing pointers can avoid copying entire data structures (such as arrays, slices, and structs), improving performance.
- Implement reference passing: Through pointer parameters, functions can directly modify external variable values without just modifying their copies.
- Consistency: Multiple functions can share and modify the same variable through pointer parameters, maintaining data consistency.
1.5 Pointers and Arrays:
Learn how to combine pointers with arrays.
- Arrays: In Go, an array is a fixed-length sequence of elements with the same type.
- Pointers pointing to array elements: A pointer can point to an element in an array, allowing indirect access and modification of the array’s elements.
1 | package main |
- Array length is fixed: The length of an array is determined at declaration time and cannot be changed dynamically. When operating on arrays using pointers, ensure that the pointer does not go out of bounds.
- Pointer arithmetic limitations: Go does not support direct pointer arithmetic, so it is not possible to traverse an array using
p++
orp--
. Instead, explicitly operate on the pointer to access different elements. - Slice alternatives: In many cases, slices can provide more flexible and convenient operations. A slice is a dynamic view of an array that allows for easier traversal and modification.
1.6 Pointers and Slices:
Understand the underlying implementation of slices and their relationship with pointers, and learn how to use pointers to operate on slices.
- Slices: A slice is a reference to a contiguous segment of an array. A slice consists of three parts: a pointer (pointing to the start of the underlying array), length (the number of elements in the slice), and capacity (the number of elements from the start to the end of the underlying array).
- Underlying array: A slice relies on an underlying array. All operations on the slice are actually operations on this underlying array.
1 | type SliceHeader struct { |
- Through pointers, you can operate on the elements of a slice. You can get the address of an element in the slice and modify its value.
1 | package main |
Slice considerations:
- Slices share underlying arrays: Multiple slices can share the same underlying array, and modifying one slice’s elements will affect other slices that share the same array.
- Avoid out-of-bounds access: When operating on slices using pointers, ensure that the pointer does not go out of bounds.
- Performance impact of slice growth: Frequent slice growth can lead to multiple memory allocations and data copies, which should be avoided.
1.7 Pointers and Structs:
Learn how to use pointers to operate on structs, as well as pointer receivers in struct methods.
- Structs: A struct is a composite data type that combines multiple fields of different types into a single unit.
- Pointers to structs: Pointers can point to structs, allowing access and modification of the struct’s fields through the pointer.
1 | package main |
As you can see, p.Name
is equivalent to (*p).Name
.
Pointer Receivers
1 | package main |
- Value Receivers vs Pointer Receivers: If a method does not need to modify the struct’s fields, it can use value receivers; if it needs to modify them, it should use pointer receivers.
- Accessing Struct Fields: Pointers can be used to conveniently access and modify struct fields, but care must be taken to avoid accessing null pointers.
1.8 Memory Allocation:
Deeply understand the usage scenarios and differences between
new
andmake
.
new
and make
Differences
new
:new
is a built-in function that allocates memory. It returns a pointer to the type’s zero value.new(T)
allocates memory for typeT
, initializes it with the type’s zero value, and returns a pointer of type*T
.make
:make
is also a built-in function, but it only creates and initializes slices, maps, and channels.make
returns an initialized (non-zero) value, not a pointer.
1 | package main |
1 | package main |
new
is used to allocate memory and return a pointer, while make
is used to create and initialize slices, maps, and channels. In actual development, choose the appropriate memory allocation method based on your needs to improve code performance and maintainability.
1.9 unsafe package:
The
unsafe
package provides some operations that bypass Go’s type safety, allowing for low-level memory operations. Although using theunsafe
package can achieve high-level functionality, it may also lead to program crashes or insecurity, so use with caution.
unsafe package - unsafe - Go Packages
The unsafe package provides an unsafe.Pointer type essence with a *int, which can point to any type:
1 | type ArbitraryType int |
The unsafe
package provides three functions:
func Sizeof(x ArbitraryType) uintptr
: returns the size ofx
in bytes, excluding the size of the content pointed to byx
.func Offsetof(x ArbitraryType) uintptr
: returns the offset of a struct member from the start of the struct, passed as an argument.func Alignof(x ArbitraryType) uintptr
: returns the alignment requirement for the type ofx
.
Note that all return types are uintptr
. uintptr
is an integer type that is large enough to hold the bit pattern of any pointer. However, it does not have pointer semantics and will be garbage-collected.
1 | // uintptr is an integer type that is large enough to hold the bit pattern of |
The unsafe.Pointer
type cannot perform arithmetic operations directly, but can be converted to uintptr
, performed arithmetic operations on uintptr
, and then converted back to a pointer
.
Here is an example of using unsafe.Pointer
:
1 | package main |
You can see the difference between uintptr
and unsafe.Pointer
:
unsafe.Pointer
is a general-purpose pointer type used for converting different types of pointers. It cannot participate in pointer arithmetic.uintptr
is used for pointer arithmetic, but does not have pointer semantics and will be garbage-collected.unsafe.Pointer
can be converted to and fromuintptr
.- However, it is not recommended to use
unsafe.Pointer
without careful consideration, as it may lead to memory truncation or access extension issues.
Here is an example of using unsafe.Pointer
incorrectly:
1 | package main |
This code is not safe and may produce unpredictable results.
More Series Articles about You Should Know In Golang:
https://wesley-wei.medium.com/list/you-should-know-in-golang-e9491363cd9a
And I’m Wesley, delighted to share knowledge from the world of programming.
Don’t forget to follow me for more informative content, or feel free to share this with others who may also find it beneficial. it would be a great help to me.
See you in the next article. 👋
中文文章: https://programmerscareer.com/zh-cn/golang-pointers/
Author: Wesley Wei – Twitter Wesley Wei – Medium
Note: Originally written at https://programmerscareer.com/golang-pointers/ at 2024-07-04 01:03. If you choose to repost or use this article, please cite the original source.
Comments