可以在swift中创建静态分配的数组吗?

时间:2016-07-11 03:58:47

标签: arrays swift static-allocation

我想在swift中创建一个结构,它具有一个固定数量的小值(例如16个浮点数)作为实例数据。要求此结构不将这些值存储在堆上,以便结构实例的地址是实例变量的地址。还要求这些值可以通过下标在结构内部访问,就像数组一样。

在C中,你只需简单地定义这种东西:

struct Matrix4x4 {
    float elements[16];
    ...
} myMatrix;

使用此代码,sizeof(Matrix4x4) == 64以及&myMatrix == &myMatrix.elements[0];在swift中,如果我类似地将elements变量定义为类型[Float],则矩阵实例仅包含指向数组,因为Array<Float>实例是存储在堆上的对象。

有没有办法在swift中获得实例变量的静态分配而不放弃类似数组的下标访问的便利性和效率?

2 个答案:

答案 0 :(得分:5)

目前,这在“纯粹的斯威夫特”中是不可能的。有一个长期的讨论 在

开始的swift-evolution邮件列表中

要求这样的特征,例如,将矩阵结构传递给C函数。 据我所知,这个建议很受欢迎,但没有具体计划 截至目前,并未列入 currently active Swift proposals

C数组

float elements[16];

作为包含16个组件的元组导入Swift:

public var elements: (Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float, Float)

目前这似乎是定义具有给定内存布局的固定大小结构的唯一方法。 来自Apple的Joe Groff写道 [swift-users] Mapping C semantics to Swift

  

Swift结构具有未指定的布局。如果依赖于特定的布局,则应该在C中定义结构并将其导入到Swift中。

later in that discussion

  

您可以保留在C中定义的结构并将其导入Swift。斯威夫特将尊重C的布局。

如果矩阵类型是在C头文件中定义的(为了简单起见,我正在使用 现在以2x2矩阵为例)

// matrix.h:
typedef struct Matrix2x2 {
    float elements[4];
} Matrix2x2;

然后将其导入到Swift中

public struct Matrix2x2 {
    public var elements: (Float, Float, Float, Float)
    public init()
    public init(elements: (Float, Float, Float, Float))
}

如上所述,Swift保留了C内存布局,使矩阵,它的 元素和第一个元素都具有相同的地址:

var mat = Matrix2x2(elements: (1, 2, 3, 4))
print(sizeofValue(mat)) // 16
withUnsafePointer(&mat) { print($0) }            // 0x00007fff5fbff808
withUnsafePointer(&mat.elements) { print($0) }   // 0x00007fff5fbff808
withUnsafePointer(&mat.elements.0) { print($0) } // 0x00007fff5fbff808

但是,元组不是可订阅的,如果元组成员有,则这是有意义的 不同种类。有关swift-evolution邮件列表的另一个讨论

将“统一元组”视为集合,这将允许下标。 不幸的是,这还没有实现。

有一些方法可以通过索引访问元组成员,例如使用Mirror()withUnsafe(Mutable)Pointer()

这是 Swift 3(Xcode 8)的可能解决方案,它似乎运行良好 并且只涉及很少的开销。 “技巧”是定义C函数 返回指向元素存储的指针:

// matrix.h:

// Constant pointer to the matrix elements:
__attribute__((swift_name("Matrix2x2.pointerToElements(self:)")))
static inline const float * _Nonnull matrix2x2PointerToElements(const Matrix2x2 * _Nonnull mat)
{
    return mat->elements;
}

// Mutable pointer to the matrix elements:
__attribute__((swift_name("Matrix2x2.pointerToMutableElements(self:)")))
static inline float * _Nonnull pointerToMutableElements(Matrix2x2 * _Nonnull mat)
{
    return mat->elements;
}

我们需要两个变体来使正确的值语义工作(下标setter需要 变量,下标getter与常量或变量一起工作)。 “swift_name”属性使编译器将这些函数作为成员导入 Matrix2x2类型的函数,比较

现在我们可以在Swift中定义下标方法:

extension Matrix2x2 {
    public subscript(idx: Int) -> Float {
        get {
            precondition(idx >= 0 && idx < 4)
            return pointerToElements()[idx]
        }
        set(newValue) {
            precondition(idx >= 0 && idx < 4)
            pointerToMutableElements()[idx] = newValue
        }
    }
}

一切都按预期工作:

// A constant matrix:
let mat = Matrix2x2(elements: (1, 2, 3, 4))
print(mat[0], mat[1], mat[2], mat[3]) // 1.0 2.0 3.0 4.0

// A variable copy:
var mat2 = mat
mat2[0] = 30.0
print(mat2) // Matrix2x2(elements: (30.0, 2.0, 3.0, 4.0))

当然你也可以定义类似矩阵的下标方法

public subscript(row: Int, col: Int) -> Float

以类似的方式。

答案 1 :(得分:0)

作为上述暗示的答案,您可以使用withUnsafeMutableBytes()assumingMemoryBound(to:)的组合将C数组视为调用范围内的快速数组。

withUnsafeMutableBytes(of: &mymatrix.elements) { rawPtr in
        let floatPtr = rawPtr.baseAddress!.assumingMemoryBound(to: Float.self)
        // Use the floats (with no bounds checking)
        // ...
        for i in 0..<10 {
            floatPtr[i] = 42.0
        }
    }