Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does `UnsafeMutablePointer.initialize()`actually do?

The following is base on my guess. Someone please point out the parts that I understand incorrectly.

If I have a class, of which an instance occupies 128 bits, called Class128Bits. And my program runs on a 64 bits computer.

First, I call let pointer = UnsafeMutablePointer<Calss128Bits>.allocate(capacity: 2) the memory layout should look like this:

000-063 064 bits chaos
064-127 064 bits chaos
128-255 128 bits chaos
256-383 128 bits chaos

If I call pointer.pointee = aClass128Bits, it crashes because the pointers in the first two grids have not been initialized yet. Accessing to what they point to leads to unpredictable results.

But if I call pointer.initialize(to: aClass128Bits, count: 2), the pointers could be initialized like this:

000-063 address to offset 128
064-127 address to offset 256
128-255 a copy of aClass128Bits
256-383 a copy of aClass128Bits

Then any accesses will be safe. However this cannot explain why UnsafeMutablePointer<Int> does not crash.


Original

The case I am facing: Crash

The pointer to Int works fine, but the one to String crashes. I know that I need to initialize it like this: Not Crash

But I can't see the reason why I need to pass "42" twice. In C, I might do something similar like this:

char *pointer = (char *)malloc(3 * sizeof(char));
memcpy(pointer, "42", 3);
free(pointer)

If allocate equals malloc, free equals deallocate, memcpy equals pointee{ set }, then what do initialize and deinitialize actually do? And why does my code crash?

like image 215
CopperCash Avatar asked Sep 27 '16 05:09

CopperCash


3 Answers

let pointer0 = UnsafeMutablePointer<String>.allocate(capacity: 1)
let pointer1 = UnsafeMutablePointer<Int>.allocate(capacity: 1)

let check the size of both

MemoryLayout.size(ofValue: pointer0) // 8
MemoryLayout.size(ofValue: pointer1) // 8

let check the value of .pointee

pointer0.pointee // CRASH!!!

while

pointer1.pointee // some random value

Why? The answer is as simple, as it can be. We allocated 8 bytes, independently from "associated" Type. Now is clear, that 8 bytes in memory are not enough to store any String. the underlying memory must be referenced indirectly. But there are some 8 random bytes there ... Loading what is in the memory with address represented by 8 random bytes as a String will most likely crash :-)

Why didn't it crash in the second case? Int value is 8 bytes long and the address can be represented as Int value.

let's try in the Playground

import Foundation

let pointer = UnsafeMutablePointer<CFString>.allocate(capacity: 1)    

let us = Unmanaged<CFString>.passRetained("hello" as CFString)

pointer.initialize(to: us.takeRetainedValue())
print(pointer.pointee)

us.release()

// if this playground crash, try to run it again and again ... -)
print(pointer.pointee)

look what it prints to me :-)

hello
(
    "<__NSCFOutputStream: 0x7fb0bdebd120>"
)

There is no miracle behind. pointer.pointee is trying to represent what is in the memory, which address is stored in our pointer, as a value of its associated type. It never crashes for Int because every 8 continues bytes somewhere in the memory can be represented as Int.

Swift use ARC, but creating the Unsafe[Mutable]Poiner doesn't allocate any memory for the instance of T, destroying it doesn't deallocate any memory for it.

Typed memory must be initialized before use and deinitialized after use. This is done using initialize and deinitialize methods respectively. Deinitialization is only required for non-trivial types. That said, including deinitialization is a good way to future-proof your code in case you change to something non-trivial

Why doesn't assignment to .pointee with Int value crash?

  1. Initialize store the address of value
  2. Assignment to pointee update the value at stored address

Without initializing It most likely will crash, only the probability is less by modifying only 8 bytes in memory at some random address.

trying this

import Darwin
var k = Int16.max.toIntMax()
typealias MyTupple = (Int32,Int32,Int8, Int16, Int16)

var arr: [MyTupple] = []
repeat {
    let p = UnsafeMutablePointer<MyTupple>.allocate(capacity: 1)
    if k == 1 {
        print(MemoryLayout.size(ofValue: p), MemoryLayout.alignment(ofValue: p),MemoryLayout.stride(ofValue: p))
    }
    arr.append(p.pointee)
    k -= 1
    defer {
        p.deallocate(capacity: 1)
    }
} while k > 0

let s = arr.reduce([:]) { (r, v) -> [String:Int] in
    var r = r
    let c = r["\(v.0),\(v.1),\(v.2),\(v.3)"] ?? 0
    r["\(v.0),\(v.1),\(v.2),\(v.3)"] = c + 1
    return r
}
print(s)

I received

8 8 8
["0,0,-95,4104": 6472, "0,0,0,0": 26295]
Program ended with exit code: 0

It doesn't look very random, is it? That explains, why the crash with the typed pointer to Int is very unlikely.

like image 139
user3441734 Avatar answered Nov 16 '22 02:11

user3441734


One reason you need initialize(), and the only one as for now maybe, is

for ARC.

You'd better think with local scope variables, when seeing how ARC works:

func test() {
    var refVar: RefType = initValue  //<-(1)
    //...
    refVar = newValue //<-(2)
    //...
    //<-(3) just before exiting the loacl scope
}

For a usual assignment as (2), Swift generates some code like this:

swift_retain(_newValue)
swift_release(_refVar)
_refVar = _newValue

(Assume _refVar and _newValue are unmanaged pseudo vars.)

Retain means incrementing the reference count by 1, and release means decrementing the reference count by 1.


But, think what happens when the initial value assignment as at (1).

If the usual assignment code was generated, the code might crash at this line:

swift_release(_refVar)

because newly allocated region for a var may be filled with garbages, so swift_release(_refVar) cannot be safely executed.

Filling the newly region with zero (null) and release safely ignoring the null could be one solution, but it's sort of redundant and not effective.

So, Swift generates this sort of code for initial value assignment:

(for already retained values, if you know ownership model, owned by you.)

_refVar = _initValue

(for unretained values, meaning you have no ownership yet.)

swift_retain(_initValue)
_refVar = _initValue

This is initialize.

No-releasing the garbage data, and assign an initial value, retaining it if needed.

(The above explanation of "usual assignment" is a little bit simplified, Swift omits swift_retain(_newValue) when not needed.)


When exiting the local scope at (3), Swift just generates this sort of code:

swift_release(_refVar)

So, this is deinitialize.


Of course, you know retaining and releasing are not needed for primitive types like Int, so initialize and deinitialize may be donothing for such types.

And when you define a value type which includes some reference type properties, Swift generates initialize and deinitialize procedures specialized for the type.


The local scope example works for the regions allocated on the stack, and initialize() and deinitialize() of UnsafeMutablePointer works for the regions allocated in the heap.

And Swift is evolving so swift, that you might find another reason for needing initialize() and deinitialize() in the future, you'd better make it a habit to initialize() and deinitialize() all allocated UnsafeMutablePointers of any Pointee types.

like image 24
OOPer Avatar answered Nov 16 '22 02:11

OOPer


From the documentation it is possible to conclude that .initialize() is a method that :

Initializes memory starting at self with the elements of source.

And .deinitialize() is a method that :

De-initializes the count Pointees starting at self, returning their memory to an uninitialized state.

We should understand that when we are using UnsafeMutablePointer we should manage memory on our own. And methods that are described above help us to do this.

So in your case lets analyze example that you provide:

let pointer = UnsafeMutablePointer<String>.allocate(capacity: 1)
// allocate a memory space

pointer.initialize(to: "42")
// initialise memory

pointer.pointee // "42"
// reveals what is in the pointee location

pointer.pointee = "43"
// change the contents of the memory

pointer.deinitialize()
// return pointer to an unintialized state

pointer.deallocate(1)
// deallocate memory

So your code crashes because you do not initialize memory and try to set value.

Previously in objective-c when we are working with objects we always use [[MyClass alloc] init]].

In this case :

alloc:

allocates a part of memory to hold the object, and returns the pointer.

init:

sets up the initial parameters of the object and returns it.

So basically .initialize() sets the value to the allocated memory part. When you create an object only with alloc you only set reference to empty memory part in the heap. When you call .initialize() you set value to this memory allocation in the heap.

Nice article about the pointers.

like image 2
Oleg Gordiichuk Avatar answered Nov 16 '22 02:11

Oleg Gordiichuk