Do you need help on a specific subject? Use the contact form (Request a blog entry) on the right hand side.

2015-11-16

Swift gotcha: Using the same pointer in two arguments

Working with buffers and pointers to buffers in Swift is surprisingly easy. But sometimes a shortcut can end up biting the hand that typed it...

Consider the following case:

func test(a: UnsafeMutablePointer<UInt8>, b: UnsafeMutablePointer<UInt8>) {
    print("a = \(a), \(a.memory), b = \(b), \(b.memory)")
    print("a3 = \(a[3])")
    print("b3 = \(b[3])")
    b[3] = 6
    a[3] = 7
    print("a3 = \(a[3])")
    print("b3 = \(b[3])")
}

var buf = [UInt8](count: 20, repeatedValue: 0)
var v: UInt8 = 40
for i in buf.startIndex ..< buf.endIndex {
    buf[i] = v++
}

test(&buf, b: &buf)

print(buf[3])

What would be the value printed by the last statement?
Of course this would not be a gotcha if that value was not 6.
Thing is, the function's arguments are pointers to an array, and in Swift arrays are passed by value. That behaviour seems to extend to pointers and the memory they point at.
This becomes clear if we look at the result of the first print statement in the function:

a = 0x00007fd422809950, 40, b = 0x00007fd42040c660, 40

It is clear that the pointers point to two different memory area's, but both these area's contain the same values. Then as the function completes, the memory area's are merged such that the area belonging to the later argument is copied over the area pointed at by the first argument.

This has a few implication: first is that inside the function we can be sure that pointers will point at different memory area's, even if we "know" that both pointers refer to the same base value.
The second is that there is copying overhead even though pointers are used.

Now, you may argue that the above is contrived, you would surely never use the same pointer twice in an argument list right?
Well...

Consider this case:

test(&buf[0], b: &buf[4])

This works as intended (when the test function uses the UnsafeMutablePointer  not UnsafePointer!), the printout shows that both pointer point to the same area:

a = 0x00007fcbe072bb10, 40, b = 0x00007fcbe072bb14, 44

When dealing with pointers we can also write &buf[4as &buf+the two are synonyms... right?

Nope... try this: test(&buf+0, b: &buf+4)

This yields: a = 0x00007fcbe072bb10, 40, b = 0x00007fcbe2009b94, 44

Whoa, suddenly the copying behaviour is back. This is very annoying when dealing with low level IO operations where it is sometimes necessary to pass back and forth pointers into buffers.

Using offsets and indicies as arguments is a good way to solve this potential problem.

Happy coding...

Did this help?, then please help out a small independent.
If you decide that you want to make a small donation, you can do so by clicking this
link: a cup of coffee ($2) or use the popup on the right hand side for different amounts.
Payments will be processed by PayPal, receiver will be sales at balancingrock dot nl
Bitcoins will be gladly accepted at: 1GacSREBxPy1yskLMc9de2nofNv2SNdwqH

We don't get the world we wish for... we get the world we pay for.

No comments:

Post a Comment