Pointer basics

The computer accesses addressable memory in bytes. Machine-level programs treat memory as a very large array of bytes, called virtual memory. Each byte of this memory is identified by a unique number, which we call an address. The collection of all these addresses is called the virtual address space.

The computer performs storage, transmission, or operations on a set of binary sequences as units called words. The binary number of bits of a word is called the word length. The virtual address space is determined by the word length of the MACHINE (CPU). If the word length is N bits, the virtual address space is 0 to 2^ N-1 32 bits. The corresponding virtual address space is 4 gigabytes (4GB).

The pointer type uses the machine’s full word length, i.e. :32position4Bytes,64position8Bytes. Pointers represent addresses, which are unique numeric identifiers for each byte of virtual storage,nThe maximum address identifier of the bit-machine virtual address space is reachable2^n-1This number identifies the address that needs to be represented and must be usednBit to store.

Swift Memory Layout

Use MemoryLayout to view the size and alignment of the Swift basic data type.

MemoryLayout<Int>.size // The value is an Int, and the continuous memory usage is 8 bytes
MemoryLayout<Int>.alignment // Memory alignment of type Int is 8 bytes
The number of bytes between the start address of an Int and the start address of the next Int
MemoryLayout<Int>.stride // The step of type Int is 8 bytes

MemoryLayout<Optional<Int>>.size // 9 bytes, optional type one byte more than normal type
MemoryLayout<Optional<Int>>.alignment / / 8 bytes
MemoryLayout<Optional<Int>>.stride / / 16

MemoryLayout<Float>.size / / 4 bytes
MemoryLayout<Float>.alignment / / 4 bytes
MemoryLayout<Float>.stride / / 4 bytes

MemoryLayout<Double>.size / / 8
MemoryLayout<Double>.alignment / / 8
MemoryLayout<Double>.stride / / 8

MemoryLayout<Bool>.size / / 1
MemoryLayout<Bool>.alignment / / 1
MemoryLayout<Bool>.stride / / 1

MemoryLayout<Int32>.size / / 4
MemoryLayout<Int32>.alignment / / 4
MemoryLayout<Int32>.stride / / 4
Copy the code

Use MemoryLayout to view the size and alignment of Swift enumerations, structures, and class types.

/// Enumeration type
enum EmptyEnum {/ / / an empty enumeration
}
MemoryLayout<EmptyEnum>.size / / 0
MemoryLayout<EmptyEnum>.alignment // all addresses are divisible by 1, so they can exist at any address.
MemoryLayout<EmptyEnum>.stride / / 1

enum SampleEnum {
   case none
   case some(Int)}MemoryLayout<SampleEnum>.size / / 9
MemoryLayout<SampleEnum>.alignment / / 8
MemoryLayout<SampleEnum>.stride / / 16

/ / / structure
struct SampleStruct {}MemoryLayout<SampleStruct>.size / / 0
MemoryLayout<SampleStruct>.alignment // all addresses are divisible by 1, so they can exist at any address.
MemoryLayout<SampleStruct>.stride / / 1

struct SampleStruct {
    let b : Int
    let a : Bool
}
MemoryLayout<SampleStruct>.size // 9 but b is 16
MemoryLayout<SampleStruct>.alignment / / 8
MemoryLayout<SampleStruct>.stride / / 16

class EmptyClass {}
MemoryLayout<EmptyClass>.size/ / 8
MemoryLayout<EmptyClass>.alignment/ / 8
MemoryLayout<EmptyClass>.stride/ / 8

class SampleClass {
    let b : Int = 2
    var a : Bool?
}
MemoryLayout<SampleClass>.size / / 8
MemoryLayout<SampleClass>.alignment/ / 8
MemoryLayout<SampleClass>.stride/ / 8

Copy the code

Explore memory layout in more detail.

Swift pointer classification

Immutable pointer types in Swift:

Pointer BufferPointer
Typed UnsafePointer<T> UnsafeBufferPointer<T>
Raw UnsafeRawPointer UnsafeRawBufferPointer

Mutable pointer types in Swift:

Pointer BufferPointer
Typed UnsafeMutablePointer<T> UnsafeMutableBufferPointer<T>
Raw UnsafeMutableRawPointer UnsafeMutableRawBufferPointer
  1. Pointer and BufferPointer: Pointer points to a single value in memory, such as Int. BufferPointer refers to multiple values (sets) of the same type in memory called buffer Pointers, such as [Int].

  2. Typed Pointers Typed provide the types that interpret the byte sequence to which the pointer points. The untyped pointer Raw accesses the most primitive byte sequence and does not provide the type of the interpreted byte sequence.

  3. Mutable and Immutable Pointers, read only. Mutable pointer, readable and writable.

Comparison of Swift and OC Pointers

Swift OC instructions
UnsafePointer<T> const T* A pointer is immutable; what it points to is mutable
UnsafeMutablePointer<T> T* Pointers and what they point to are mutable
UnsafeRawPointer const void* A constant pointer to an unknown type
UnsafeMutableRawPointer void* Pointer to an unknown type

Use untyped (Raw) Pointers

Example:

Use an untyped pointer to access memory and write three integers (ints).

let count = 3
let stride = MemoryLayout<Int>.stride ///8 bytes, storage space per instance
let byteCount = stride * count
let alignment = MemoryLayout<Int>.alignment
Request uninitialized memory with the specified byte size and alignment
let mutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
defer {
    mutableRawPointer.deallocate()
}
// initialize memory
mutableRawPointer.initializeMemory(as: Int.self, repeating: 0, count: byteCount)
/// the RawPointer start position stores the integer 15
mutableRawPointer.storeBytes(of: 0xFFFFFFFFF, as: Int.self)
/// There are two ways to offset RawPointer's starting position to an integer position (8 bytes) and store an integer there:
(mutableRawPointer + stride).storeBytes(of: 0xDDDDDDD, as: Int.self)
mutableRawPointer.advanced(by: stride*2).storeBytes(of: 9, as: Int.self)
/// Use 'load' to view the value of a specific offset address in memory
mutableRawPointer.load(fromByteOffset: 0, as: Int.self) / / 15
mutableRawPointer.load(fromByteOffset: stride, as: Int.self) / / 9
/// Access memory as a collection of bytes via 'UnsafeRawBufferPointer'
let buffer = UnsafeRawBufferPointer.init(start: mutableRawPointer, count: byteCount)
for (index,byte) in buffer.enumerated(){/// iterate over the value of each byte
    print("index:\(index),value:\(byte)")}/ / output
//index:0,value:255 index:8,value:221 index:16,value:9
//index:1,value:255 index:9,value:221 index:17,value:0
//index:2,value:255 index:10,value:221 index:18,value:0
//index:3,value:255 index:11,value:13 index:19,value:0
//index:4,value:15 index:12,value:0 index:20,value:0
//index:5,value:0 index:13,value:0 index:21,value:0
//index:6,value:0 index:14,value:0 index:22,value:0
//index:7,value:0 index:15,value:0 index:23,value:0

Copy the code

UnsafeRawBufferPointer represents a collection of bytes in an area of memory that can be accessed as bytes.

Uses Typed Pointers

For the above example, typed Pointers are used:

let count = 3
let stride = MemoryLayout<Int>.stride ///8 bytes, storage space per instance
let byteCount = stride * count
let alignment = MemoryLayout<Int>.alignment
// apply for uninitialized memory to store a specified number of instances of the specified type.
let mutableTypedPointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
// initialize memory
mutableTypedPointer.initialize(repeating: 0, count: count)
defer {
    mutableTypedPointer.deinitialize(count: count)
    mutableTypedPointer.deallocate()
}
/// Store the first value
mutableTypedPointer.pointee = 15
/// Store the second value
/// 'antecedent' returns a pointer to the next continuous instance
mutableTypedPointer.successor().pointee = 10
mutableTypedPointer[1] = 10
/// Store the third value
(mutableTypedPointer + 2).pointee = 20
mutableTypedPointer.advanced(by: 2).pointee = 30
/// read from memory
mutableTypedPointer.pointee / / 15
(mutableTypedPointer + 2).pointee / / 30
(mutableTypedPointer + 2).predecessor().pointee / / 10
/// Access memory as a collection of bytes via 'UnsafeRawBufferPointer'
let buffer = UnsafeRawBufferPointer.init(start: mutableTypedPointer, count: byteCount)
for (index,byte) in buffer.enumerated(){/// iterate over the value of each byte
    print("index:\(index),value:\(byte)")}/ / output
//index:0,value:15 index:8,value:10 index:16,value:30
//index:1,value:0 index:9,value:0 index:17,value:0
//index:2,value:0 index:10,value:0 index:18,value:0
//index:3,value:0 index:11,value:0 index:19,value:0
//index:4,value:0 index:12,value:0 index:20,value:0
//index:5,value:0 index:13,value:0 index:21,value:0
//index:6,value:0 index:14,value:0 index:22,value:0
//index:7,value:0 index:15,value:0 index:23,value:0
Copy the code

RawPointer is converted to TypedPointer

/// Untyped Pointers apply for uninitialized memory in bytes
let  mutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
defer {
    mutableRawPointer.deallocate()
}
/// Raw converts the volume of bytes Typed
let mutableTypedPointer = mutableRawPointer.bindMemory(to: Int.self, capacity: count)
/// initialization, not required
mutableTypedPointer.initialize(repeating: 0, count: count)
defer {
    /// Just de-initialize and the first 'defer' will handle the pointer's 'deallocate'
    mutableTypedPointer.deinitialize(count: count)
}
/ / / storage
mutableTypedPointer.pointee = 10
mutableTypedPointer.successor().pointee = 40
(mutableTypedPointer + 1).pointee = 20
mutableTypedPointer.advanced(by: 2).pointee = 30

/// traverse, using typed 'BufferPointer' to traverse
let buffer = UnsafeBufferPointer.init(start: mutableTypedPointer, count: count)
for item in buffer.enumerated() {
    print("index:\(item.0),value:\(item.1)")}/ / output
//index:0,value:10
//index:1,value:20
//index:2,value:30
Copy the code

UnsafeBufferPointer

represents a collection of elements stored consecutively in memory that can be accessed as elements. For example, if Element’s true type is Int16, the value of Int16 stored in memory will be accessed.

Calls a function that takes a pointer

When a function is called that takes a pointer as an argument, it is possible to pass a compatible pointer type through an implicit conversion, or to pass a pointer to a variable or a pointer to an array through an implicit bridge.

Constant pointer as argument

When calling a function that takes a pointer to UnsafePointer

, we can pass the following arguments:

  • UnsafeMutablePointer UnsafePointer < Type >, < Type > or AutoreleasingUnsafeMutablePointer < Type >, according to the need to be implicitly converted to UnsafePointer < Type >.

  • If Type is Int8 or UInt8, you can pass instances of String; The string is automatically converted to UTF8 and a pointer to that UTF8 buffer is passed to the function

  • The variable variable, attribute, or subscript of Type Type is passed to the function by adding the address & to the left. (Implicit bridge)

  • An array of Type ([Type]) is passed to the function with a pointer to the beginning of the array. (Implicit bridge)

The following is an example:

/// define a function that takes' UnsafePointer
      
        'as an argument
      
func functionWithConstTypePointer(_ p: UnsafePointer<Int8>) {
    / /...
}
/// Pass 'UnsafeMutablePointer
      
       ' as an argument
      
let mutableTypePointer = UnsafeMutablePointer<Int8>.allocate(capacity: 1)
mutableTypePointer.initialize(repeating: 10, count: 1)
defer {
    mutableTypePointer.deinitialize(count: 1)
    mutableTypePointer.deallocate()
}
functionWithConstTypePointer(mutableTypePointer)
/// pass 'String' as an argument
let str = "abcd"
functionWithConstTypePointer(str)
/// pass input-output variables as arguments
var a : Int8 = 3
functionWithConstTypePointer(&a)
/// pass the array of '[Type]'
functionWithConstTypePointer([1.2.3.4])
Copy the code

When calling a function that takes a pointer argument UnsafeRawPointer, we can pass the same argument as UnsafePointer

, but without the Type constraint:

The following is an example:

/// define a function that takes' UnsafeRawPointer 'as an argument
func functionWithConstRawPointer(_ p: UnsafeRawPointer) {
    / /...
}
/// Pass 'UnsafeMutablePointer
      
       ' as an argument
      
let mutableTypePointer = UnsafeMutablePointer<Int8>.allocate(capacity: 1)
mutableTypePointer.initialize(repeating: 10, count: 1)
defer {
    mutableTypePointer.deinitialize(count: 1)
    mutableTypePointer.deallocate()
}
functionWithConstRawPointer(mutableTypePointer)
/// pass 'String' as an argument
let str = "abcd"
functionWithConstRawPointer(str)
/// pass input-output variables as arguments
var a = 3.0
functionWithConstRawPointer(&a)
/// Pass an array of any type
functionWithConstRawPointer([1.2.3.4] as [Int8])
functionWithConstRawPointer([1.2.3.4] as [Int16])
functionWithConstRawPointer([1.0.2.0.3.0.4.0] as [Float])
Copy the code

Mutable pointer as argument

When calling a function that takes a pointer to UnsafeMutablePointer

, we can pass the following arguments:

  • UnsafeMutablePointer<Type>The value of the.
  • theTypeType of variable, attribute, or subscript, by adding the fetch address character to the left&Is passed to the function. (Implicit bridge)
  • aTypeType mutable array ([Type]) is passed to the function as a pointer to an array. (Implicit bridge)

The following is an example:

/// define a function that takes' UnsafeMutablePointer
      
        'as an argument
      
func functionWithMutableTypePointer(_ p: UnsafePointer<Int8>) {
    / /...
}
/// Pass 'UnsafeMutablePointer
      
       ' as an argument
      
let mutableTypePointer = UnsafeMutablePointer<Int8>.allocate(capacity: 1)
mutableTypePointer.initialize(repeating: 10, count: 1)
defer {
    mutableTypePointer.deinitialize(count: 1)
    mutableTypePointer.deallocate()
}
functionWithMutableTypePointer(mutableTypePointer)
/// Pass a variable of Type 'Type'
var b : Int8 = 10
functionWithMutableTypePointer(&b)
/// Pass a variable of Type '[Type]'
var c : [Int8] = [20.10.30.40]
functionWithMutableTypePointer(&c)
Copy the code

Similarly, when you call a function that takes a pointer argument UnsafeMutableRawPointer, you can pass the same argument as UnsafeMutablePointer

, but without the Type constraint. The following is an example:

/// define a function that takes' UnsafeMutableRawPointer 'as an argument
func functionWithMutableRawPointer(_ p: UnsafeMutableRawPointer) {
    / /...
}
/// Pass 'UnsafeMutablePointer
      
       ' as an argument
      
let mutableTypePointer = UnsafeMutablePointer<Int8>.allocate(capacity: 1)
mutableTypePointer.initialize(repeating: 10, count: 1)
defer {
    mutableTypePointer.deinitialize(count: 1)
    mutableTypePointer.deallocate()
}
functionWithMutableRawPointer(mutableTypePointer)
/// Pass any type of variable
var b : Int8 = 10
functionWithMutableRawPointer(&b)
var d : Float = 12.0
functionWithMutableRawPointer(&d)
/// Pass any type of mutable array
var c : [Int8] = [20.10.30.40]
functionWithMutableRawPointer(&c)
var e : [Float] = [20.0.10.0.30.0.40.0]
functionWithMutableRawPointer(&e)
Copy the code

Important knowledge points

The pointer created through implicit bridging of an instance or of an array’s elements is only valid during The execution of the called function. Escaping the pointer to use after the execution of the function is undefined behavior. In particular, do not use implicit bridging when calling an UnsafePointer/UnsafeMutablePointer/UnsafeRawPointer/UnsafeMutableRawPointerinitializer.

var number = 5
let numberPointer = UnsafePointer<Int> (&number)
// Accessing 'numberPointer' is undefined behavior.
var number = 5
let numberPointer = UnsafeMutablePointer<Int> (&number)
// Accessing 'numberPointer' is undefined behavior.
var number = 5
let numberPointer = UnsafeRawPointer(&number)
// Accessing 'numberPointer' is undefined behavior.
var number = 5
let numberPointer = UnsafeMutableRawPointer(&number)
// Accessing 'numberPointer' is undefined behavior.
Copy the code

what is undefined behavior?

Undefined behavior in programming languages can introduce difficult to diagnose bugs and even lead to security Back in your App.

func functionWithPointer(_ p: UnsafePointer<Int>) {
    let mPointer = UnsafeMutablePointer<Int>.init(mutating: p)
    mPointer.pointee = 6;
}
var number = 5
let numberPointer = UnsafePointer<Int> (&number)
functionWithPointer(numberPointer)
print(numberPointer.pointee) / / 6
print(number)//6 There are bugs that are difficult to check
number = 7
print(numberPointer.pointee) / / 7
print(number)/ / 7
Copy the code

Memory access

For any type of value in Swift,Swift provides global functions to access their Pointers or bytes in memory directly.

Access the pointer

Swift can access a pointer to any value through the following functions.

withUnsafePointer(to:_:)/// Only access, not modify
withUnsafeMutablePointer(to:_:)// It can be accessed and modified
Copy the code

Example:

/ / / only access
let temp : [Int8] = [1.2.3.4] 
withUnsafePointer(to: temp) { point in
    print(point.pointee)
}
/// Standard mode: access & modify
var temp : [Int8] = [1.2.3.4]
withUnsafeMutablePointer(to: &temp) { mPointer in
    mPointer.pointee = [6.5.4];
}
print(temp) / / / [6, 5, 4)
Copy the code

SwiftIf you want to change it by pointer, it must be a variable, and the variable must be marked as when calling the above methodinoutParameters, that is, variables added to the left&. Constant value, cannot beinoutParameter in the form of an access pointer.

Error Example 1:

/// Error: access & modify
var temp : [Int8] = [1.2.3.4]
withUnsafePointer(to: &temp) { point in
    let mPointer = UnsafeMutablePointer"[Int8] >.init(mutating: point)
    mPointer.pointee = [6.5.4];
}
Copy the code

Here’s why:

A closure that takes a pointer to value as its sole argument. If the closure has a return value, that value is also used as the return value of the withUnsafePointer(to:_:) function. The pointer argument is valid only It is undefined behavior to try to mutate through the pointer argument by converting it to UnsafeMutablePointer or any other mutable pointer type. If you need to mutate the argument through the pointer, use withUnsafeMutablePointer(to:_:) instead.

Example 2:

var tmp : [Int8] = [1.2.3.4]
1 / / / errors
let pointer = withUnsafePointer(to: &tmp, {$0})
let mPointer = UnsafeMutablePointer"[Int8] >.init(mutating: pointer)
mPointer.pointee = [7.8.9]
2 / / / errors
let mutablePointer = withUnsafeMutablePointer(to: &tmp) {$0}
mutablePointer.pointee = [6.5.4.3]
Copy the code

Here’s why:

The pointer argument to body is valid only during the execution of withUnsafePointer(to:_:) or withUnsafeMutablePointer(to:_:). Do not store or return the pointer for later use.

When accessing memory using the above methods, remember not to return or store closure parametersbodyFor later use.

Access to the byte

Swift can access bytes in memory of any value through the following functions.

withUnsafeBytes(of:_:)/// Only access, not modify
withUnsafeMutableBytes(of:_:)/// it can be accessed and modified
Copy the code

Example:

/// Only access, not modify
var temp : UInt32 = UInt32.max
withUnsafeBytes(of: temp) { rawBufferPointer in
    for item in rawBufferPointer.enumerated() {
        print("index:\(item.0),value:\(item.1)")}}/ / output
index:0,value:255
index:1,value:255
index:2,value:255
index:3,value:255

/// Access & modify,
var temp : UInt32 = UInt32.max / / 4294967295
withUnsafeMutableBytes(of: &temp) { mutableRawBuffer in
    mutableRawBuffer[1] = 0;
    mutableRawBuffer[2] = 0;
    mutableRawBuffer[3] = 0;
    //mutableRawBuffer[5] = 0; /// crash
}
print(temp) / / / 255
Copy the code

Examples of errors:

/// Access & modify. This method is risky and should be avoided
var temp : UInt32 = UInt32.max / / 4294967295
withUnsafeBytes(of: &temp) { rawBufferPointer in
    let mutableRawBuffer = UnsafeMutableRawBufferPointer.init(mutating: rawBufferPointer)
    / / / small end of the sequence
    mutableRawBuffer[1] = 0;
    mutableRawBuffer[2] = 0;
    mutableRawBuffer[3] = 0;
    for item in mutableRawBuffer.enumerated() {
        print("index:\(item.0),value:\(item.1)")}}print(temp) / / / 255
Copy the code

Here’s why:

A closure that takes a raw buffer pointer to the bytes of value as its sole argument. If the closure has a return value, that value is also used as the return value of the withUnsafeBytes(of:_:) function. The buffer pointer argument is Valid only for the duration of the closure’s execution. It is undefined behavior to attempt to mutate through the pointer by conversion to UnsafeMutableRawBufferPointer or any other mutable pointer type. If you want to mutate a value by writing through a pointer, use withUnsafeMutableBytes(of:_:) instead.

The resources

Developer.apple.com/documentati…

www.raywenderlich.com/7181017-uns…

www.vadimbulavin.com/swift-point…