Question about C function returning char pointer

A couple of questions about the following:

import <Darwin/Glibc/whatever>

func cwd() -> UnsafeMutablePointer<Int8>? {
    var buf = [Int8](repeating: 0, count: 256)
    guard let cwdRef = getcwd(&buf, buf.count) else { return nil }
    print(String(cString: cwdRef))
    return cwdRef
}

var buf = [Int8](repeating: 0, count: 256)
print(String(cString: getcwd(&buf, buf.count)))

if let cwdPtr = cwd() {
    print(String(cString: cwdPtr))
}

Are any of these truly unsafe? Specifically, is it possible for me to be referring to a "dead pointer" at any time? I'm thinking that its probably not safe to return cwdRef from the cwd() function, but it "seems to work".

Also, why does the print(String(cString: getcwd(&buf, buf.count))) not give me an error about referencing an unwrapped optional? In the other cases where I refer to the results of the call to getcwd() I get this warning if I don't unwrap the result.

It depends on the ownership of the string, but as a general rule, no, this isn't safe, and I'd expect this not to work. Generally, a C function that returns a string will either return a pointer to constant memory if the string is a compile-time literal (i.e. a const char *someString = "string"), in which case you don't need to worry about memory management, or as a heap-allocated string, in which case you need to free() it once you're done with the memory.

In cases like getcwd where you can also pass it a buffer, it will simply write to that buffer, and you as the caller retain ownership of it. When that buffer goes out of scope the memory will also be deallocated, and you'll be left with a dangling pointer.

This is probably clearer if the function is equivalently written like this:

func cwd() -> UnsafeMutablePointer<Int8>? {
    var buf = [Int8](repeating: 0, count: 256)
    guard let _ = getcwd(&buf, buf.count) else { return nil }
    print(String(cString: cwdRef))
    return buf.withUnsafeMutableBufferPointer { $0.baseAddress }
}

It should be fairly obvious in this case that the returned pointer will not be valid outside of the withUnsafeMutableBufferPointer block.

As for why it's working for now: the correct C string will remain at that memory address until a new allocation happens that writes over it. It just happens that nothing overwrites that memory in between returning the pointer and accessing its contents.

Note that String(cString:) will copy the string passed into it, so it's safe to return the constructed String.

getcwd (and other C APIs that haven't been annotated) are imported as implicitly unwrapped optionals since there's no way to know whether the pointer is nil or not. As soon as you assign an IUO to a variable, the compiler will infer it to be an Optional unless you specify otherwise.

Thanks. So the returning of the pointer is not guaranteed to work because buf has gone out of scope, but referring to the pointer prior to buf going out of scope is OK. Cool, that's what I figured.

As for the IUO, that's not something I've really used, so I didn't think of it. I assume that if getcwd() did return a null value then the attempt to use it to initialize String(cString:) would fail.

Thanks much.

That’s basically correct, although note that (if I’m remembering Swift semantics properly) the scope of variables is until their last usage; in other words, since the last reference to buf is within getcwd the compiler is free to deallocate it at any point after that statement. There’s no guarantee that the data is valid in the print(String(cString: cwdRef)) line, for example.

If you want to guarantee it lives past that point you can use withExtendedLifetime.

Ah, OK. So is it best practice to directly allocate an unsafe mutable pointer and then manually deallocate it when I'm done? I'd hoped to avoid needing to do that.

I'd personally write it something like this:

func cwd() -> String? {
    var buf = [Int8](repeating: 0, count: 256)
    return buf.withUnsafeMutableBufferPointer { buf -> String? in
          guard let cwdRef = getcwd(buf.baseAddress!, buf.count) else { return nil }
          return String(cString: cwdRef)
    }
}

or

func cwd() -> [Int8]? {
    var buf = [Int8](repeating: 0, count: 256)
    let cwdRef = buf.withUnsafeMutableBufferPointer { buf in
          return getcwd(buf.baseAddress!, buf.count)
    }
    guard let cwdRef = cwdRef else { return nil }
    return buf
}

depending on whether you want to return a string or a buffer.

EDIT: Actually,

func cwd() -> [Int8]? {
    var buf = [Int8](repeating: 0, count: 256)
    guard getcwd(&buf, buf.count) != nil else { return nil }
    return buf
}

is fine for the buffer case since it's guaranteed not to go out of scope.

1 Like

Makes sense, @Torust. Thanks!

Foundation has APIs to access the current directory:

The implementation uses a buffer of length PATH_MAX + 1. It doesn't do error checking for ENOENT, etc.

Thanks. The use of getcwd() was only so I could have a concrete example to use in asking by question about behavior.

1 Like

I tried out your example in Xcode 10.1, Swift 4.2.1 and I'm getting an error:

Use of unresolved identifier 'cwdRef'

cwdRef is never being assigned in the code you pasted. in guard let _ = getcwd(&buf, buf.count) else { return nil }, change the _ to cwdRef.
I think they just deleted the variable assignment from their original example but forgot to remove the print statement.

2 Likes

Thanks. This now compiles. But for using it e.g. display the result of cwd() in a textView I still have to convert it into a String, otherwise I'm getting

Cannot assign value of type '[Int8]?' to type 'String?'

I suggest you have a look through the rest of the thread. In particular, the example you’re using is an example of a way not to implement it.

I gave an example or how you could have the function return String earlier in the thread, although I haven’t actually tried running it myself.

func cwd() -> String? {
    var buf = [Int8](repeating: 0, count: 256)
    return buf.withUnsafeMutableBufferPointer { buf -> String? in
        guard let cwdRef = getcwd(buf.baseAddress!, buf.count) else { return nil }
        return String(cString: cwdRef)
    }
}

The above now works and returns "/". It's in an IOS on iPhone.

Could you explain in a few words, what the code does?
buf is declared as some Array of Int8 (Bytes).
Why does it contain three "return" statements to return just what getcwd returns?

I'd probably read myself more about the Swift language to understand "guard" and the use of "let _".

--
Christoph

I'm no expert, but I'll try to answer your questions.

buf, as you say, is an array of 256 Int8's (signed bytes). You invoke the withUnsafeMutableBufferPointer method on it, and pass a closure that takes as input, in this case, an "inout UnsafeMutableBufferPointer<Int8>" (which is also in this case named buf, but is not the same buf that is defined earlier) and returns a String. (Probably the outer buf should be renamed, maybe to bufferArray or something...)

Basically (??), when the closure is invoked it is passed as an inout parameter the an "unsafe mutable buffer" that contains the address(baseAddress) and size (count) of the "backing storage" of the array. Since getcwd() takes a pointer to a signed byte (array) and the size of the the array, those are the parameters you pass to it.

getcwd(), assuming there is no error, populates the buffer with the value of the current working directory, along with a trailing "null" character. It returns the address of the buffer (the same value as buf.baseAddress).

Since there is the possibility of this being a "null" address (binary zeroes), which would occur in the case of an error, you have to code for that possibility. That's where guard comes in. The guard says that if the assignment (let) of the result of getcwd() to the variable named cwdRef fails (because getcwd() has returned a null pointer, which Swift treats as a nil value) then the closure should return the nil value.

If the assignment succeeds (the guard "passes") then the result (cwdRef) is passed to the call to String(cString:), which creates a Swift String based on the contents of the C String that cwdRef points to.

The outer return returns the results if the closure as the result of the call to cwd().

So to more specifically answer why there are three return statements, the first one, as stated previously, returns the result of the closure. The two within the closure return from the closure itself, either with the value nil or the created String.

As for the let _ = in an earlier example, that simply says to ignore the result of the call to (in this case) getcwd(). His example would have compiled if he had invoked String(cString:) with the parameter buf instead of cwdRef:
print(String(cString: buf))

Honestly, while I've described a lot here, once you get each item "under your thumb" individually it's "relatively simple" to understand. :slight_smile:

One further thing to note is that the "inner" buf is automatically deallocated upon the closure being existed, and the "outer" buf is automatically deallocated upon the cwd() functioning being exited. That's actually why I used an Array initially. I could have used dynamic allocation of an UnsageMutableBufferPointer directly. Then I would have had to make sure to explicitly deallocate it before returning from cwd().

For what its worth, the following might be somewhat preferable. It returns a String instead of an Optional<String> (String?), and results in a "fatal error" if there is an error returned from the call to getcwd().

func cwd() -> String {
    var bufferArea = [Int8](repeating: 0, count: 256)
    return bufferArea.withUnsafeMutableBufferPointer { buf -> String in
        guard let cwdRef: UnsafeMutablePointer = getcwd(buf.baseAddress, buf.count) 
        else { fatalError("Unexpected nil result when calling getcwd() [errno = \(errno)]") }
        return String(cString: cwdRef)
    }
}

Note: I chose to not explicitly unwrap buf.baseAddress because the char * parameter in the C function is automatically imported as an "implicitly unwrapped optional" field. I'll let you look that one up. :slight_smile:

If you are interested in actually causing the call to getcwd() to fail, set the count parameter to 0 instead of 256 when creating the bufferArea array.

Thanks for your explanations.

is the inner buf, right? So to say a local variable as in {} in C.

String(cString: cwdRef)

is a method of the Class String, saying it instantiates a String made from cString? Or is it some kind of "cast"?

Yes, that's the "inner buf". It's really a parameter passed to the closure by the call to the withUnsafeMutableBufferPointer method. That is, that method, when invoked on the "outer" buf, makes the "inner" buf a UnsafeMutableBufferPointer that represents the backing storage of the outer buf.

The String(cString: cwdRef) does as you say, instantiates a new String based on the information pointed to by cwdRef.

I had a thought to use the Data type for this. Here is the result. Aside from using Data I am also depending on the fact that a C function returning the char * datatype is automatically treated as being an implicitly unwrapped Optional.

func cwd() -> String {
    let wdlen = 256
    var bufferData = Data(count: wdlen)
    return bufferData.withUnsafeMutableBytes { String(cString: getcwd($0, wdlen)) }
}

A few comments:

  • Here withUnsafeMutableBytes generates an UnsafeMutablePointer instead of an UnsafeMutablePointerBuffer, which means that the field doesn't "contain" its length (count). Seems to me that Data should also have a version of withUnsafe... that works with a buffer pointer...
  • Which brings up the concern that this method seems to actually be misnamed. The free function withUnsafeMutableBytes(of:) works with UnsafeMutableRawBufferPointer, which is different in two ways: 1) It's a "buffer pointer", not just a pointer, and 2) It's a raw (buffer) pointer, not a typed one. So rather inconsistent!
  • I don't know if this is any more or less efficient than using an Array.
  • Seems to me that Data, or something like it, should be in the Swift standard library rather than part of Foundation.
Terms of Service

Privacy Policy

Cookie Policy