-1

I've built a little structure which holds a pseudo-C-String UnsafeMutablePointer. Now I wanted to add a way to get the length of the String, but something behaved very strange:

struct Char {
    let value: CChar

    init(_ char: CChar) {
        self.value = char
    }
}

extension Char: CustomStringConvertible {
    var description: String {
        return String(UnicodeScalar(UInt8(bitPattern: self.value)))
    }
}

extension Char: Equatable {
    public static func ==(lhs: Char, rhs: Char) -> Bool {
        return lhs.value == rhs.value
    }
}

struct CharPointer {
    let pointer: UnsafeMutablePointer<Char>

    init(_ string: String) {
        let chars = string.cString(using: .utf8)!.map { Char($0) }
        self.pointer = UnsafeMutablePointer(mutating: chars)
    }

    subscript(_ offset: Int) -> Char {
        get { return self.pointer[offset] }
        set { self.pointer[offset] = newValue }
    }

    var length: Int {
        var ptr = self.pointer

        while ptr.pointee != Char(0) {
            ptr += 1
        }

        return ptr - self.pointer
    }
}

let pointer = CharPointer("Hello World!")

print(pointer[0])

// print(pointer.length)

If I don't use the length property, everything works just fine and the print statement prints an "H". But if I try to access pointer.length, it prints a newline, although I use the property after the print statement, and the length is 0. Is this a bug or have I made a mistake?

Josef Zoller
  • 921
  • 2
  • 8
  • 24
  • 2
    `UnsafeMutablePointer(mutating: chars)` gives you a dangling pointer, as the pointer produced by the implicit argument conversion is only valid for the duration of the initialiser call – dereferencing that pointer gives you *undefined behaviour*. Really Swift should forbid inout-to-pointer argument conversions for UnsafePointer.init params (I'm actually currently looking into implementing that). – Hamish Aug 16 '18 at 21:39

1 Answers1

0

Thanks to @Hamish I found a solution, I just initialize a second pointer with the pointer from the array:

init(_ string: String) {
    let chars = string.cString(using: .utf8)!.map { Char($0) }
    let pointer = UnsafeMutablePointer(mutating: chars)
    let pointer2 = UnsafeMutablePointer<Char>.allocate(capacity: chars.count)
    pointer2.initialize(from: pointer, count: chars.count)

    self.pointer = pointer2
}
Josef Zoller
  • 921
  • 2
  • 8
  • 24
  • Technically speaking, that will still yield undefined behaviour as you're still escaping a temporary pointer argument – you could avoid that by just saying `pointer.initialize(from: chars, count: chars.count)` directly. Though now you've allocated memory, you'll need to deallocate it somewhere (or else you'll get memory leaks). You could make `CharPointer` a `class` and do that in the `deinit` – but why not store a `Char` array in your `CharPointer` instead of a`UnsafeMutablePointer`? Then Swift will take care of the buffer's memory for you and you can keep `CharPointer` a `struct`. – Hamish Aug 16 '18 at 22:25
  • Oh I could also use the normal swift string type...No, I'm just testing things with UnsafePointers out. – Josef Zoller Aug 16 '18 at 23:14