Why are Pointers unsafe
- For example, when we create an object, we need to allocate memory space in the heap. But this memory space has a finite declaration period, which means that if we use a pointer to this memory space, if the current memory space reaches its lifetime (reference count is 0), then our current pointer becomes undefined.
- The memory space we create is bounded, for example we create an array of size 10, which we access through a pointer
index = 11
Is crossing the line and visiting an unknown space. - Pointer types inconsistent with memory value types are also unsafe.
Second, the pointer
Pointers in Swift fall into two categories
typed pointer
: Specifies the data type pointer, that isUnsafePointer<T>
, where T representsThe generic
raw pointer
: pointer to an unspecified data type (native pointer), that isUnsafeRawPointer
Comparison with Pointers in OC:
OC | Swift | paraphrase |
---|---|---|
const T * |
unsafePointer<T> |
Pointers and what they point toimmutable |
T * |
unsafeMutablePointer |
Pointers and what they point tovariable |
const void * |
unsafeRawPointer |
The memory region to which the pointer points is undetermined |
void * |
unsafeMutableRawPointer |
Same as above |
2.1 Use of native Pointers
Let’s see how we can use raw Pointer to store 4 integer data. We use UnsafeMutableRawPointer:
/ / open the memory, 32 bytes allocated space is 8 bytes (Int), the size of alignment is 8 bytes aligned let p = UnsafeMutableRawPointer. The allocate (byteCount: 32, alignment: 8) // Save value for I in 0.. <4 {// Specify the number of current moves, that is, I * 8 p.anced (by: I * 8).storeBytes(of: I + 1, as: int.self)} // The value for I in 0.. Print ("index: \(I), value: ") print("index: \(I), value: ") \(value)")} // Allocate ()Copy the code
Run the program to view the execution result, and the value can be printed normally:
MemoryLayout can be used to dynamically obtain the stride size when moving the number of steps. Modify the code as follows:
p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i + 1, as: Int.self)
Copy the code
MemoryLayout
. Size // size refers to the actual size of the current type MemoryLayout
. Stride // stride translation is a stride, MemoryLayout
. Alignment // Indicates the alignment mode of memory, whether the alignment is 1-byte or 4-byte
Run the program and still print normally:
2.2 Use of type Pointers
We can use the withUnsafePointer(to:) method to get the address of the basic data type:
Var age = 18 let p = withUnsafePointer(to: &age) {PTR in return PTR} print(p) 0x0000000100008058Copy the code
2.2.1 How do I Access a Value pointed to by a Pointer
We can access variable values via the pointer’s pointee property:
Var age = 18 let p = withUnsafePointer(to: &age){$0} print(p.pointeeCopy the code
2.2.2 How do I Change the Value pointed to by a Pointer
-
Indirect changes
Var age = 18 age = withUnsafePointer(to: &age) {PTR in return ptr.pointee + 12} print(age) The following output is displayed: 30Copy the code
-
Through withUnsafeMutablePointer
Var age = 18 withUnsafeMutablePointer(to: &age) {PTR in ptr.pointee += 12} print(ageCopy the code
-
Create UnsafeMutablePointer by allocate
Var age = 18 let PTR = UnsafeMutablePointer<Int>. Allocate (capacity: 1) // Initialize PTR. Initialize (to: Age) ptr.deinitialize(count: 1) ptr.pointee += 12 print(ptr.pointee) // Release ptr.deallocate(Copy the code
When creating UnsafeMutablePointer by allocate, note the following
initialize
与deinitialize
Use in pairsdeinitialize
In thecount
At the time of applicationcapacity
Need to beconsistent
- Must be after use
deallocate
2.2.3 Accessing structure objects through Pointers
Let’s define a structure
Struct SSLTeacher {var age = 18 var height = 1.85} var t = SSLTeacher()Copy the code
We then create a pointer using UnsafeMutablePointer and access the structure object T in three ways:
Let PTR = UnsafeMutablePointer<SSLTeacher>. Allocate (capacity: allocate) PTR. Initialize (to: SSLTeacher()) [1] = SSLTeacher(age: 20, height: Print (PTR [0]) print(PTR [1]) print(PTR. Pointee) print(PTR +1).pointee Forerunner () print(ptr.pointee) // The precursor must be the same as the allocation ptr.deinitialize(count: // Locate ptr.deallocate()Copy the code
Run the program and view the execution result:
2.3 Macho pointer operation case
Next, we will familiarize ourselves with the operation of Pointers through examples. We will use Pointers to read property names in Macho, class names, and methods in vTable.
2.3.1 Obtaining a Pointer to a classDescriptor
To get a pointer to a classDescriptor, run the following code:
class SSLTeacher{ var age: Int = 18 var name: String = "SSL"} var mhHeaderPtr = _dyLD_GET_image_header (0 mhHeaderPtr_IntRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: Var setCommond64Ptr = getSegByName ("__LINKEDIT") var linkBaseAddress: UInt64 = 0 if let vmaddr = setCommond64Ptr? .pointee.vmaddr, let fileOff = setCommond64Ptr? .pointe. fileoff{linkBaseAddress = vmaddr-fileoff} //__swift5_types section pFile address var size: UInt = 0 var ptr = getsectdata("__TEXT", "__swift5_types", &size) // __swift5_types section pFile address in Macho UInt64 = 0 if let unwrappedPtr = ptr{ let intRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: UnwrappedPtr)) offset = intrepresentation-linkBaseAddress} // DataLO's memory address var dataLoAddress = Offset // DataLO pointer var dataLoAddressPtr = withUnsafePointer(to: &dataloAddress){return $0} var dataLoContent = UnsafePointer<UInt32>. Init (bitPattern: Int(exactly: dataLoAddress) ?? 0)? .pointee // Descriptor Address in Macho Let typeDescOffset = UInt64(dataLoContent!) + offset -linkBaseAddress // Descriptor address var typeDescAddress = typeDescOffset + mhHeaderPtr_IntRepresentation //print(typeDescAddress) struct TargetClassDescriptor{ var flags: UInt32 var parent: UInt32 var name: Int32 var accessFunctionPointer: Int32 var fieldDescriptor: Int32 var superClassType: Int32 var metadataNegativeSizeInWords: UInt32 var metadataPositiveSizeInWords: UInt32 var numImmediateMembers: UInt32 var numFields: UInt32 var fieldOffsetVectorOffset: UInt32 var Offset: UInt32 var methods: UInt32} // Get pointer to Descriptor Let classDescriptor = UnsafePointer<TargetClassDescriptor>. Init (bitPattern: Int(exactly: typeDescAddress) ?? 0)? . Pointee/print/print (classDescriptor) / / * * * * * * * * * * the output * * * * * * * * * * Optional (SwiftTest. TargetClassDescriptor (flags:...). )Copy the code
- VM Address: Virtual Memory Address, the Virtual Memory Address of the segment, its location in Memory
- VM Size: Virtual Memory Size, the Virtual Memory Size of the segment
- File Offset: indicates the Offset of the segment in virtual memory
- File Size: the Size of the segment in virtual memory
- Address Space Layout Random is a security protection technology against buffer overflow. By randomizing the Layout of linear areas such as heap, stack and shared library mapping, it increases the difficulty of the attacker to predict the destination Address and prevents the attacker from locating the attack code. A technique to prevent overflow attacks
- Relevant Macho:
2.3.2 Getting the class name
Add the following code to print the class name:
if let name = classDescriptor? .name{ let nameOffset = Int64(name) + Int64(typeDescOffset) + 8 let nameAddress = nameOffset + Int64(mhHeaderPtr_IntRepresentation) if let cChar = UnsafePointer<CChar>.init(bitPattern: Int (nameAddress)) {print (String (cstrings: cChar))}} / / * * * * * * * * * * the output * * * * * * * * * * SSLTeacherCopy the code
2.3.3 Obtaining the Attribute Name
Add the following code to print the property name:
/ / fieldDescriptor attribute address let filedDescriptorRelaticveAddress = typeDescOffset + 16 + mhHeaderPtr_IntRepresentation struct FieldDescriptor { var mangledTypeName: Int32 var superclass: Int32 var Kind: UInt16 var fieldRecordSize: UInt16 var numFields: UInt32 // var fieldRecords: [FieldRecord] } struct FieldRecord{ var Flags: UInt32 var mangledTypeName: Int32 var fieldName: UInt32} // fieldDescriptor offset let fieldDescriptorOffset = UnsafePointer<UInt32>. Init (bitPattern: Int(exactly: filedDescriptorRelaticveAddress) ?? 0)? The pointee / / fieldDescriptor memory address let fieldDescriptorAddress = filedDescriptorRelaticveAddress + UInt64(fieldDescriptorOffset!) // fieldDescriptor pointer let fieldDescriptor = UnsafePointer< fieldDescriptor >. Init (bitPattern: Int(exactly: fieldDescriptorAddress) ?? 0)? .pointee // Get attribute name for I in 0.. <fieldDescriptor! .numfields {// FieldRecord size is 12, so the stride is 12 let stride: UInt64 = UInt64(I * 12) // Move 16 bytes to fieldRecords let fieldRecordAddress = fieldDescriptorAddress + Stride + 16 let fieldNameRelactiveAddress = UInt64(2 * 4) + fieldRecordAddress - linkBaseAddress + mhHeaderPtr_IntRepresentation let offset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: fieldNameRelactiveAddress) ?? 0)? .pointee let fieldNameAddress = fieldNameRelactiveAddress + UInt64(offset!) - linkBaseAddress if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(fieldNameAddress)){ print(String(cString: CChar)}} / / * * * * * * * * * * the output * * * * * * * * * * age nameCopy the code
2.3.4 Obtaining the Method Name
Modify the SSLTeacher class to add three methods and add the code to print the method names:
@interface SSLTest : NSObject + (void)callImp:(IMP)imp; @end @implementation SSLTest + (void)callImp:(IMP)imp { imp(); } @end class SSLTeacher { func teach1() { print("testch1"); } func teach2() { print("testch2"); } func teach3() { print("testch3"); }} let numVTables = classDescriptor? .methods struct VTable { var kind: UInt32 var offset: UInt32 } for i in 0.. <numVTables! {// Calculate offset let vTableOffSet = Int(typeDescOffset) + MemoryLayout<TargetClassDescriptor>. Size + Int(I) * MemoryLayout<VTable>. Size // Obtain the VTable address. Let vTableAddress = mhHeaderPtr_IntRepresentation + UInt64(vTableOffSet) let vTable = UnsafePointer<VTable>.init(bitPattern: Int(exactly: vTableAddress) ?? 0)? ImpAddress = vTableAddress + 4 + UInt64(vTable! .offset) - linkBaseAddress SSLTest.callImp(IMP(bitPattern: UInt(impAddress))!) ; } / / * * * * * * * * * * the output * * * * * * * * * * testch1 testch2 testch3Copy the code
3. Memory binding
Swift provides three different apis to bind/rebind Pointers:
3.1 assumingMemoryBound(to:)
The purpose of this API is to tell the compiler what type to expect (allowing the compiler to bypass type checking without actually converting the type), as shown in the following example:
- The above error says will
A tuple type
The pointer assigned toThe Int type
Pointer, type mismatch - But in fact tuples are value types, so essentially the memory space holds data of type Int
- The following through
assumingMemoryBound(to:)
Function to modify
3.2 bindMemory (to: capacity:)
Used to change the type of memory binding, and if the memory does not already have a type binding, it will be bound to that type for the first time. Otherwise, rebind the type and all the values in memory will change to that type.
Func testPointer(_ p: UnsafePointer<Int>) {print(p[0]) print(p[1])} let tuple = (10,20) withUnsafePointer(to: tuple){ (tupleStr: UnsafePointer<(Int, Int)>) in testPointer(UnsafeRawPointer(tupleStr).bindMemory(to: Int. Self, capacity: 1))Copy the code
3.3 withMemoryRebound (to: capacity: body:)
WithMemoryRebound (to: Capacity :body:) is used to temporarily bind the memory type. See the following example
func testPointer(_ p: UnsafePointer<Int8>) { print(p) } let uint8Ptr = UnsafePointer<UInt8>.init(bitPattern: 10) uint8Ptr? .withMemoryRebound(to: Int8.self, capacity: 1) { (int8Ptr: UnsafePointer<Int8>) in testPointer(int8Ptr) }Copy the code
Four, strong reference
Swift uses an automatic reference counting (ARC) mechanism to track and manage memory.
Add the following code first:
class SSLTeacher { var age: Int = 18 var name: String = "Kody"} var t = SSLTeacher() print(unmanaged.passunretained (t as AnyObject).toopaque ()) // Fixed the memory address of the instance object NSLog("end")Copy the code
Breakpoint debugging, printing memory:
The last 8 bytes of the first 16 bytes of the memory address of the instance object are used to store the reference count. The value in this case is 0x3.
4.1 Source code analysis refCounts
Start with the definition of reference counts. Open the source code to find refCounts in heapObject.h:
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
Copy the code
RefCounts is of type InlineRefCounts.
typedef RefCounts<InlineRefCountBits> InlineRefCounts; typedef RefCounts<SideTableRefCountBits> SideTableRefCounts; template <typename RefCountBits> class RefCounts { std::atomic<RefCountBits> refCounts; . }Copy the code
InlineRefCounts is an alias to RefCounts
. RefCounts is a template class that accepts InlineRefCountBits:
Take a look at the definition of InlineRefCountBits:
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
Copy the code
RefCountBitsT is another template class. RefCountIsInline is either true or false:
Note the bits member variable, which is defined by Type in RefCountBitsInt.
Click on Type to view:
What is the current reference count when we create an instance object?
Check the refCounts:
This is the RefCountBitsT method:
- StrongExtraCount is 0, unownedCount is 1
- StrongExtraRefCountShift is 33, PureSwiftDeallocShift is 0, and UnownedRefCountShift is 1
- So: 0 << 33 =
0
, 1 << 0 =1
, 1 << 1 =2
- 2 = 0 | 1 |
0x0000000000000003
, which explains where the reference count is above0x3
The reason why
4.2 Memory distribution for reference counting
Create 3 breakpoints and print reference count memory at breakpoints:
- As can be seen from above, when the strong reference is 1, the reference count is stored at high because it is moved 33 bits to the left
33
位 - When the strong reference is 2, the reference count is stored at high because it has been moved 33 bits to the left
34
位
The memory distribution of reference counting is as follows:
- UnownedRefCount: no primary reference count
- IsDeinitingMask: Whether destructing is taking place
- StrongExtraRefCount: Strong application count
4.3 Adding strong Reference Counts
Open the source code and find the related increment function for strong reference counting:
From the above function, we can see that the strong reference count is incremented by moving 1 33 bits to the left and then adding it to the bits, which is consistent with our analysis of reference count memory distribution above.
Weak references
Looking at the code below, teacher and subject generate circular references. In Swift, there are weak references and undirected references to resolve circular references
5.1 Overview of Weak References
class SSLTeacher { var age: Int = 18 var name: String = "ssl" var subject:SSLSubject? } class SSLSubject { var subjectName: String var subjectTeacher: SSLTeacher init(_ subjectName: String, _ subjectTeacher: SSLTeacher) { self.subjectName = subjectName self.subjectTeacher = subjectTeacher } } var teacher = SSLTeacher() var Subject = sslsubject.init (" math ",teacher) teacherCopy the code
A weak reference does not maintain a strong reference to the instance it references, and thus does not prevent ARC from releasing the referenced instance. This feature prevents references from becoming circular strong references. When declaring an attribute or variable, prefix it with the weak keyword to indicate a weak reference.
Since a weak reference does not strongly retain a reference to the instance, it is possible that the instance was freed and the weak reference still refers to the instance. Therefore, ARC automatically sets weak references to nil when the referenced instance is freed. Since weak references need to allow their value to be nil, they must be of optional type.
Weak Example codes for using weak:
class SSLTeacher {
var age: Int = 18
var name: String = "ssl"
weak var subject:SSLSubject?
}
Copy the code
5.2 Source analysis SideTable derivation
What function is called when you add the weak keyword
You can see that the swift_weakInit function is called, which is available in heapObject.cpp
Declaring a weak variable is equivalent to defining a WeakReference object. Click to see nativeInit
Weak essentially creates a SideTable that calls the allocateSideTable function.
5.3 Source analysis SideTable
Check the allocateSideTable:
HeapObjectSideTableEntry vs. InlineRefCounts
View the definition of HeapObjectSideTableEntry:
HeapObjectSideTableEntry Uses SideTableRefCounts to store reference counts.
typedef RefCounts<InlineRefCountBits> InlineRefCounts; typedef RefCounts<SideTableRefCountBits> SideTableRefCounts; class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline> { uint32_t weakBits; . }Copy the code
As you can see, SideTableRefCounts and InlineRefCounts share the same template class RefCounts
. They both inherit from RefCountBitsT. The subclass SideTableRefCounts has an additional weakBits member variable.
The following uses LLDB debugging to check the storage of reference counts in different cases
0xc0000000200c437c
Is the value after weak reference- Here is the weak reference creation function
- Puts the address of the current side object into a 64-bit bitfield
- Two identifier bits, 62 and 63, are 1
- will
0xc0000000200c437c
Reverse operation, get hash table memory address0x100621BE0
- View the structure of the hash table through LLDB debugging
0x0000000000000003
Is a strongly referenced value0x0000000000000002
Is the value of a weak reference
Unreferenced
Like weak references, an ownerless reference does not strongly reference an instance. Unlike weak references, however, an ownerless reference assumes that the instance will always have a value.
When we access an undirected reference like this, it’s kind of like accessing a wild pointer, because we always assume a value, so we crash the program
According to the official Apple documentation, we must use weak when we know that the life cycles of two objects are not related. In contrast, non-strongly referenced objects that have the same or longer lifetime as strongly referenced objects should use unowned.
Circular references to closures
7.1 Closure Circular Reference in Swift
Closures capture external variables by default in Swift, see the following code:
Var age = 18 let closure = {age += 1} Closure () print(ageCopy the code
As you can see from the results, changes made to variables inside the closure will change the values of the original variables outside.
There is also a problem. If we define a closure inside a class, the current closure will capture our current instance object while accessing properties.
Look at the following code. The code in deinit is printed, and here is the case when the instance is released normally:
class SSLTeacher { var age: Int = 18 deinit {print("SSLTeacher deinit")}} func testARC() {let t = SSLTeacher()} testARC(Copy the code
Teacher and closure generate circular references, and the code in deinit is not printed:
class SSLTeacher {
var age: Int = 18
var testClosure:(() -> ())?
deinit {
print("SSLTeacher deinit")
}
}
func testARC() {
let t = SSLTeacher()
t.testClosure = {
t.age += 1
}
}
testARC()
Copy the code
The destructor is not called, indicating that the teacher is not released, resulting in a circular reference.
7.2 Resolving Circular References
Weak resolve circular references:
class SSLTeacher { var age: Int = 18 var testClosure:(() -> ())? deinit { print("SSLTeacher deinit") } } func testARC() { let t = SSLTeacher() t.testClosure = { [weak t] in t! .age += 1}} testARC() Output: SSLTeacher deinitCopy the code
As you can see, with the weak modifier, the contents of the closure will print normally, and we can also solve this problem by using unowned:
func testARC() {
let t = SSLTeacher()
t.testClosure = { [unowned t] in
t.age += 1
}
}
testARC()
Copy the code
7.3 Capturing a List
What is a capture list? By default, closure expressions capture constants and variables from the range around them and strongly reference those values. You can use the capture list to show controls on how values are captured in closures.
Before the argument list, the capture list is written as a comma-enclosed list of expressions, enclosed in square brackets. If you use a capture list, you must use the in keyword.
Variables in the parameter list are not strongly referenced, see the following example code:
Closure () var age = 1 var height = 0.0let closure = {[age] in print(age) print(height)} age = 10 height = 1.85 closure() // The output is 1, 1.85Copy the code