Swift and C++ memory question

I can't understand the reason...

Error during code execution: Thread 2: EXC_BAD_ACCESS

slv.hpp

struct RoutingData {
    int locationsCount;
    int64_t const * const * distanceMatrix;
    int64_t const * const * durationMatrix;
};

main.cpp

void Solve(const struct RoutingData *routingData) {
std::vector<std::vector<int64>> durations (routingData->locationsCount, std::vector<int64>(routingData->locationsCount, 0LL));
    for (std::size_t fromNode = 0; fromNode <  routingData->locationsCount; fromNode++) {
        for (std::size_t toNode = 0; toNode <  routingData->locationsCount; toNode++) {
            durations[fromNode][toNode] = routingData->durationMatrix[fromNode][toNode];
        }
    }

}

or

void Solve(const struct RoutingData *routingData) {
    std::vector<std::vector<int64>> durations (routingData->locationsCount, std::vector<int64>(routingData->locationsCount, 0LL));
    printf("%lld\n", routingData->durationMatrix[0][0]);
}

Solve function called from Swift:

var s = RoutingData()
        s.locationsCount = Int32(self.distanceResponse.distances.count)
        s.distanceMatrix = UnsafePointer(self.distanceResponse.distances.map{UnsafePointer($0.map{Int64($0)})})
        s.durationMatrix = UnsafePointer(self.distanceResponse.durations.map{UnsafePointer($0.map{Int64($0)})})
        Solve(&s)

In debugger:

Before vector init:
*durationMatrix const int64_t *const    0x102ce5650 0x0000000102ce5650
After:
*durationMatrix const int64_t *const    NULL    0x0000000000000000

You're making pointers to the contents of Arrays, and then those arrays immediately get deallocated because they're not referenced anymore. The Array-to-pointer conversion is also only valid for the duration of the call where the Array is passed as an argument…which in this case is the call to UnsafePointer's initializer. (We have an open bug to add a warning for this kind of code.)

There's not a great way to safely build C arrays of pointers to C arrays from a Swift Array-of-Arrays; the best I could come up with involves manually allocating some C arrays and cleaning them up afterwards.

let distances: [UnsafeMutableBuffer<Int64>] = self.distanceResponse.distances.map {
  let buffer = UnsafeMutableBuffer<UInt64>.allocate(capacity: $0.count)
  buffer.initialize(from: $0.lazy.map { Int64($0) })
  return buffer
}
defer {
  distances.forEach { $0.deallocate() }
}
// similar for "durations"
distances.withUnsafeBufferPointer { distancesBuffer in
  durations.withUnsafeBufferPointer { durationsBuffer in
    assert(distancesBuffer.count == durationsBuffer.count)
    var s = RoutingData(
      locationsCount: Int32(distancesBuffer.count),
      distanceMatrix: distancesBuffer.baseAddress
      durationMatrix: durationsBuffer.baseAddress)
    Solve(&s)
  }
}

That's a lot messier, to be sure, but it makes all the lifetimes explicit.

Thank you for your reply!
I will definitely try your solution tomorrow.
What do you mean about manually allocating C arrays? Function that takes size of array allocate array and return pointer (**) to Swift?

I'm referring to the use of UnsafeMutableBuffer<UInt64>.allocate (and only thinking about a single level of pointer here). If you had a single-dimensional array, I'd suggest just using Swift's Array type, which (as shown for the top-level arrays) can provide a pointer to its contents within a bounded scope.* But since you can't get pointers to every sub-Array of a top-level Array in this way, I gave up on trying to use Array for the inner arrays, and instead allocated my own memory.

* Swift.Array also supports being implicitly converted to a pointer when used as an argument, but the pointer isn't guaranteed to be valid after the call is over, so it's only useful as a shorthand for the bounded scope version

1 Like