Это немного сложно: ваша строка на самом деле связана с Objective- C NSString *
, а не C char *
:
(lldb) p str
(const char *) $0 = 0x3cbe9f4c5d32b745 ""
(lldb) p (id)str
(NSTaggedPointerString *) $1 = 0x3cbe9f4c5d32b745 @"Test"
(если вы интересно, почему это NSTaggedPointerString
, а не просто NSString
, эта статья - отличное чтение - короче говоря, строка достаточно короткая, чтобы храниться непосредственно в байтах переменной-указателя а не в объекте в куче.
Глядя на исходный код для withVaList
, мы видим, что представление va_list
типа определяется его реализацией Свойство _cVarArgEncoding
протокола CVarArg
. Стандартная библиотека имеет некоторые реализации этого протокола для некоторых базовых c целочисленных и указательных типов , но для String
нет ничего Итак, кто конвертирует нашу строку в NSString
?
Поиск по репозиторию Swift на GitHub, мы обнаруживаем, что виновником является Foundation :
//===----------------------------------------------------------------------===//
// CVarArg for bridged types
//===----------------------------------------------------------------------===//
extension CVarArg where Self: _ObjectiveCBridgeable {
/// Default implementation for bridgeable types.
public var _cVarArgEncoding: [Int] {
let object = self._bridgeToObjectiveC()
_autorelease(object)
return _encodeBitsAsWords(object)
}
}
На простом английском sh: любой объект, который можно связать с Objecti. ve- C кодируется как vararg путем преобразования в объект Objective- C и кодирования указателя на этот объект. C varargs не являются типобезопасными, поэтому ваш test_va_arg_str
просто предполагает, что это char*
, и передает его puts
, что дает сбой.
Так это ошибка? Я так не думаю - я полагаю, что такое поведение, вероятно, преднамеренно для совместимости с такими функциями, как NSLog
, которые чаще используются с объектами Objective- C, чем с C. Тем не менее, это, безусловно, удивительная ловушка и, вероятно, одна из причин, по которой Swift не любит позволять вам вызывать C варианты c функции.
Вы захотите обойти это, вручную преобразовав ваши строки в C -строки. Это может показаться немного уродливым, если у вас есть массив строк, которые вы хотите преобразовать, не делая ненужных копий, но вот функция, которая должна уметь это делать.
extension Collection where Element == String {
/// Converts an array of strings to an array of C strings, without copying.
func withCStrings<R>(_ body: ([UnsafePointer<CChar>]) throws -> R) rethrows -> R {
return try withCStrings(head: [], body: body)
}
// Recursively call withCString on each of the strings.
private func withCStrings<R>(head: [UnsafePointer<CChar>],
body: ([UnsafePointer<CChar>]) throws -> R) rethrows -> R {
if let next = self.first {
// Get a C string, add it to the result array, and recurse on the remainder of the collection
return try next.withCString { cString in
var head = head
head.append(cString)
return try dropFirst().withCStrings(head: head, body: body)
}
} else {
// Base case: no more strings; call the body closure with the array we've built
return try body(head)
}
}
}
func withVaListOfCStrings<R>(_ args: [String], body: (CVaListPointer) -> R) -> R {
return args.withCStrings { cStrings in
withVaList(cStrings, body)
}
}
let argsStr: [String] = ["Test", "Testing", "The test"]
withVaListOfCStrings(argsStr) { listPtr in
test_va_arg_str(Int32(argsStr.count), listPtr)
}
// Output:
// Printing 3 strings...
// Test
// Testing
// The test