How to pass a pointer to a pointer safely?

I am working with launch_activate_socket function and have some trouble understanding whether I am using pointers in Swift correctly.

The second argument of the launch_activate_socket function is named fds and has a type of UnsafeMutablePointer<UnsafeMutablePointer<Int32>>?. From the manual page it says that launch_activate_socket writes its output to the memory pointed to by the fds pointer. Skipping other arguments for brevity, here are some options I came up with:

let fds = UnsafeMutablePointer<UnsafeMutablePointer<Int32>>.allocate(capacity: 1) // is passing 0 appropriate here?
launch_activate_socket("name", fds, &fdsCount)
defer { fds.deallocate() }
var fds = UnsafeMutablePointer<Int32>.allocate(capacity: 0) // is passing 1 appropriate here? 
launch_activate_socket("name", &fds, &fdsCount)
// not sure if I am supposed to call fds.deallocate() here?
var fds = unsafeBitCast(0, to: UnsafeMutablePointer<Int32>.self)
launch_activate_socket("name", &fds, &fdsCount)
// does fds.deallocate() even make sence here?

Does passing fds using ampersand result in fds pointing to the stack variable? Does instantiating the pointer using allocate(capacity:) result in a heap allocation? What is the correct way to pass fds to that function that doesn't cause a memory leak?

1 Like

The way you want to handle this is:

var fds: UnsafeMutablePointer<CInt>? = nil
launch_activate_socket("name", &fds, &fdsCount)
defer {
    free(fds)
}

If you read the manual page carefully you’ll see that launch_activate_socket claims to allocate the memory for fds and then hand ownership to you. This requires that you free the memory yourself, but you don’t need to allocate it. To that end, you can stack-allocate a pointer (initialized to nil) and use the & operator to pass it to the call. launch_activate_socket will write into that pointer value and set it to the right value.

3 Likes

Unfortunately the suggested code does not compile for me. I am trying it out in Xcode 12.5 with default toolchain in a "Command Line Tool" template:

import Foundation

var fdsCount: Int = 0
var fds: UnsafeMutablePointer<CInt>? = nil
launch_activate_socket(
    "name",
    &fds, // Value of optional type 'UnsafeMutablePointer<CInt>?' (aka 'Optional<UnsafeMutablePointer<Int32>>') must be unwrapped to a value of type 'UnsafeMutablePointer<CInt>' (aka 'UnsafeMutablePointer<Int32>')
    &fdsCount
)
1 Like

Interesting, apparently the pointer is non-nullable. I don't think that annotation can possibly be right, and I recommend using Feedback Assistant to file a report against it.

Nonetheless, you can fix that, but it is somewhat awkward. The fix is to do:

var fdsCount: Int = 0
var fds: UnsafeMutablePointer<CInt> = .init(bitPattern: 0xdeadbeef)
launch_activate_socket(
    "name",
    &fds,
    &fdsCount
)
precondition(fds != .init(bitPattern: 0xdeadbeef))

Here we're using a sentinel value that we can detect if the code fails to set this pointer.

1 Like

Thank you for the suggestion. Do I understand correctly that in the suggested code calling fds.deallocate() is inappropriate because the function is going to write back to the stack allocated memory?

No, you must call fds.deallocate: it is your responsibility to do so.

The confusion here is likely because you are working with pointers-to-pointers. This can make it a bit hard to see what's going on. I'll try to break it down briefly.

In C when you want a function argument you can write back into, you must take that argument as a pointer. This is because C is pass-by-value: when you call a function you copy the argument into the callee, and any changes to it are not reflected in the caller. Consider this code:

void first(int x) {
    x += 1;
}

void second(void) {
    int x = 1;
    first(x);
    printf("%d\n", x);
}

When we execute this code we'd expect the printed result to be 1. This is because the value of x passed to first is an independent copy of the outer variable x, and so cannot mutate it.

If first wishes to write back to that value, it must accept a pointer instead, like so:

void first(int *x) {
    *x += 1;
}

void second(void) {
    int x = 1;
    first(&x);
    printf("%d\n", x);
}

Here we will expect the printed result to be 2: we passed a pointer to our stack-allocated x into first, which it mutated.

So, what happens if first wants to return us an array of ints, instead of just a single one? An array of int in C is represented either as int * or int[] (these are two equivalent ways to spell the same thing). Remember that in C to write a value of a given type into the argument requires that we accept a pointer to it, so to write an int * back to the caller we will need to accept an argument of int **: a pointer to a pointer to an int (or alternatively, a pointer to an array of int). That would look like this:

void first(int **x) {
    *x = malloc(10);
 
    for int(i = 0; i < 10; i++) {
        (*x)[i] = i
    }
}

void second(void) {
    int *x = NULL;
    first(&x);

    for (int k = 0; k < 10; k++) {
        printf("%d ", x[k]);
    }
    printf("\n");
}

This will print out "0 1 2 3 4 5 6 7 8 9".

This is exactly what the suggested code is doing. So, let's ask the question: who is responsible for freeing fds? Well, in our smaller example the name of fds is x, but it's of the same type: int *. Does that point to stack memory here?

No, it does not, it points to the memory allocated by malloc. The pointer is on the stack, but the data it points to is on the heap and must be freed. Additionally, as first has already returned, it can only be second's responsibility to free the memory.

Again, the manual page states this explicitly:

The second argument, upon output, will point to an array of integers whose count is filled into the third argument upon success. This array represents all the sockets that launchd(8) created corresponding to the entry in the job's Sockets dictionary. Depending on the properties specified, a single Sockets entry may have multiple descriptors created for it (one for IPv4 and one for IPv6, for example). This array is allocated on the heap, and it is the caller's responsibility to call free(3) to dispose of the memory when it is no longer needed.

I omitted the free when I updated the example: that's my mistake. You absolutely must still free that memory. We did not allocate any memory on the stack.

7 Likes

Thank you for the detailed explanation. It is now much clearer to me what is going on.

I still have a bit of confusion as to what happens if I call UnsafeMutablePointer.allocate(capacity:). Could initializing var fds using UnsafeMutablePointer<CInt>.allocate(capacity: 0) be an alternative to using UnsafeMutablePointer<CInt>(bitPattern: sentinelValue)? Is my understanding correct that that I can use the pointer returned from allocate(capacity: 0) as a dummy that can be safely overwritten by launch_activate_socket?

Here is the code I am thinking about:

var fdsCount: Int = 0
var fds: UnsafeMutablePointer<CInt> = UnsafeMutablePointer<CInt>.allocate(capacity: 0)
let originalFds = fds
launch_activate_socket(
    "name",
    &fds,
    &fdsCount
)
precondition(fds != originalFds)
defer { fds.deallocate() }

It can be, but you need to be very careful. Right now the code you've written leaks memory via the original value of fds: the call to launch_activate_socket will overwrite it, and so you won't free that pointer.

The way to fix it is to free both:

var fdsCount: Int = 0
var fds: UnsafeMutablePointer<CInt> = UnsafeMutablePointer<CInt>.allocate(capacity: 0)
let originalFds = fds
launch_activate_socket(
    "name",
    &fds,
    &fdsCount
)
precondition(fds != originalFds)
defer { fds.deallocate() }
defer { originalFds.deallocate() }

This is, however, less efficient than using the sentinel value.

1 Like