7

How to add offset for array for memcpy(...) invocation?

I have array of String :

var source = ["a","b","c","d"]
var dest = [String](count:n, repeatedValue: "")
memcpy(&dest, source, UInt(2 * sizeof(String))

This copy ["a","b"] to dest. I'ts obvious. How can i copy ["b", "c"] ?

Vitaliy L
  • 572
  • 4
  • 20

3 Answers3

8

Do not use memcpy or other low-level "C" operators on objects. That will not work for many reasons.

Use the slice operator:

var source = ["a","b","c","d"]
var dest = Array(source[1...2])
println("dest: \(dest)")

output:

dest: [b, c]

Unicode is handled correctly:

var source = ["", "", "a","b","c","d"]
var dest = Array(source[1...2])
println("dest: \(dest)")

output:

dest: [, a]

zaph
  • 111,848
  • 21
  • 189
  • 228
  • Thanks for your answer. It helps me alot. It's good and fast approach but still little bit slower than memcpy(). – Vitaliy L Aug 01 '14 at 13:31
  • @user2245247 memcpy will not necessarily work and certainly not for unicode emoji, surrogate pairs and flags where the character are multiple UTF-16 units. While the implementation of String appears to be UTF-16 that may change. The Swift code is slower than memcpy because it properly handles characters that are multiple UTF-16 units in size. Per Donald Knuth: "premature optimization is the root of all evil". – zaph Aug 01 '14 at 15:22
  • Would you mind taking a look at my answer below, and let me know if you think this is too dangerous? – RenniePet Feb 11 '17 at 19:07
3

I'm still new to Swift, and use of methods with "Unsafe" in the name still worries me, but I'm fairly sure this is a usable technique for calling memcpy() and specifying an offset for the destination and/or source address. But this only works for byte arrays, i.e., [UInt8]. Definitely not for strings, as explained by @zaph.

public class SystemMisc {

   /// Wrapper for the memcpy() method that allows specification of an offset for the destination
   /// and/or the source addresses.
   ///
   /// This version for when destination is a normal Swift byte array.
   ///
   /// - Parameters:
   ///   - destPointer:    Address for destination byte array, typically Swift [UInt8].
   ///   - destOffset:     Offset to be added to the destination address, may be zero.
   ///   - sourcePointer:  Address for source byte array, typically Swift [UInt8].
   ///   - sourceOffset:   Offset to be added to the source address, may be zero.
   ///   - byteLength:     Number of bytes to be copied.
   public static func memoryCopy(_ destPointer : UnsafeRawPointer, _ destOffset : Int,
                                 _ sourcePointer : UnsafeRawPointer, _ sourceOffset : Int,
                                 _ byteLength : Int) {

      memoryCopy(UnsafeMutableRawPointer(mutating: destPointer), destOffset,
                 sourcePointer, sourceOffset, byteLength)
   }


   /// Wrapper for the memcpy() method that allows specification of an offset for the destination 
   /// and/or the source addresses.
   ///
   /// This version for when destination address is already available as an UnsafeMutableRawPointer, 
   /// for example if caller has used UnsafeMutableRawPointer() to create it or is working with 
   /// unmanaged memory. The destPointer argument may also be a converted pointer, as done by the 
   /// above wrapper method.
   ///
   /// - Parameters:
   ///   - destPointer:    Address for destination byte array, see above notes.
   ///   - destOffset:     Offset to be added to the destination address, may be zero.
   ///   - sourcePointer:  Address for source byte array, typically Swift [UInt8].
   ///   - sourceOffset:   Offset to be added to the source address, may be zero.
   ///   - byteLength:     Number of bytes to be copied.
   public static func memoryCopy(_ destPointer : UnsafeMutableRawPointer, _ destOffset : Int,
                                 _ sourcePointer : UnsafeRawPointer, _ sourceOffset : Int,
                                 _ byteLength : Int) {

      memcpy(destPointer.advanced(by: destOffset),
             sourcePointer.advanced(by: sourceOffset),
             byteLength)
   }
}

And here's some test code:

  // Test the memoryCopy() method, using extra UnsafeMutableRawPointer conversion

  let destArray1 : [UInt8] = [ 0, 1, 2, 3 ]  // Note - doesn't need to be var
  let sourceArray1 : [UInt8] = [ 42, 43, 44, 45 ]

  SystemMisc.memoryCopy(destArray1, 1, sourceArray1, 1, 2)

  assert(destArray1[0] == 0 && destArray1[1] == 43 && destArray1[2] == 44 && destArray1[3] == 3)


  // Test the memoryCopy() method, providing UnsafeMutableRawPointer for destination

  var destArray2 : [UInt8] = [ 0, 1, 2, 3 ]
  let sourceArray2 : [UInt8] = [ 42, 43, 44, 45 ]

  let destArray2Pointer = UnsafeMutableRawPointer(&destArray2)

  SystemMisc.memoryCopy(destArray2Pointer, 1, sourceArray2, 1, 2)

  assert(destArray2[0] == 0 && destArray2[1] == 43 && destArray2[2] == 44 && destArray2[3] == 3)
RenniePet
  • 11,420
  • 7
  • 80
  • 106
  • Thank you for actually answering the question. Yes, this is extremely dangerous and shouldn't be used, but this is what warnings are for. Even if you find zero legitimate use cases, it's still pretty interesting to take a peek under the hood. Just my two cents. – imas145 Aug 05 '18 at 16:41
0

First of all, there's something none of the writers seem to have understood: an array of object (here String instances) do not store the content but a reference to this object. Therefore UTF-8, UTF-16, whatever has nothing to do with it. What the backing array actually contains is pointers (ie addresses == unsigned integers). Aside from that, unless an array in swift is an actual array in memory, you shouldn't use memcpy on it, even more so if it is backed by an NSArray!

Nonetheless, to answer the original question that seems to be working perfectly and makes me think that in this case the Swift Array is a contiguous zone of memory here is what you should do:

source and dest are pointers to contiguous memory zones: the first object being at the base address, the second @+sizeof(type), the nth element at @+(n-1)*sizeof(type).

All you have to do is specify the write offset for dest, in your particular case 0, and the offset in source, in your case 1.

Renaud
  • 1
  • But Swift won't let you specify an offset on the operands of the memcpy() call. – RenniePet Feb 11 '17 at 02:12
  • Strings are value types, not references. On the other hand, trying to copy a string using memcpy, if you manage to do it somehow, will copy the bits, but get you into serious trouble later. – gnasher729 Feb 12 '17 at 22:14
  • As much as I find the answer above to be mostly valid AFAIK `Array` is not guaranteed to be a contiguous zone of memory, on the other hand `ContiguousArray` is – bartlomiej.n Nov 13 '18 at 07:41
  • @bartlomiej.n You're right, referring to the documentation of Array, it depends on its backing. By Swift i was meaning not an NSArray. By the way, thanks for the information. – Renaud Dec 07 '18 at 19:51