Skip to content

结构体、联合体与枚举

源:Structs and Unions - Kotlin Native

C 语言的结构体、联合体和枚举是组织数据的基本方式。Kotlin/Native 为这些类型提供了完整的映射和类型安全的访问方式。

结构体 (Struct)

基础结构体映射

C 结构体在 Kotlin 中映射为同名类型,字段可以直接访问:

c
// point.h
struct Point {
    double x;
    double y;
};

struct Point create_point(double x, double y);
double distance(struct Point* p1, struct Point* p2);
kotlin
@OptIn(ExperimentalForeignApi::class)
import kotlinx.cinterop.*

fun usePoint() {
    // 创建结构体实例
    memScoped {
        val point = alloc<Point>()
        
        // 直接访问字段
        point.x = 10.0
        point.y = 20.0
        
        println("Point: (${point.x}, ${point.y})")
        
        // 获取指针
        val ptr: CPointer<Point> = point.ptr
        
        // 通过指针访问
        println("Via pointer: (${ptr.pointed.x}, ${ptr.pointed.y})")
    }
}

结构体初始化

kotlin
@OptIn(ExperimentalForeignApi::class)
// 方法1:使用 alloc
memScoped {
    val point = alloc<Point>().apply {
        x = 5.0
        y = 10.0
    }
}

// 方法2:使用 C 函数创建
val point = create_point(5.0, 10.0)

// 方法3:在堆上分配
val point = nativeHeap.alloc<Point>().apply {
    x = 5.0
    y = 10.0
}
// 使用后必须释放
nativeHeap.free(point)

嵌套结构体

嵌套结构体的字段可以通过点号链式访问:

c
// C 定义
struct Rectangle {
    struct Point topLeft;
    struct Point bottomRight;
};
kotlin
@OptIn(ExperimentalForeignApi::class)
fun useRectangle() {
    memScoped {
        val rect = alloc<Rectangle>()
        
        // 访问嵌套结构体
        rect.topLeft.x = 0.0
        rect.topLeft.y = 0.0
        rect.bottomRight.x = 100.0
        rect.bottomRight.y = 50.0
        
        // 计算面积
        val width = rect.bottomRight.x - rect.topLeft.x
        val height = rect.bottomRight.y - rect.topLeft.y
        val area = width * height
        
        println("Rectangle area: $area")
    }
}

结构体指针

kotlin
@OptIn(ExperimentalForeignApi::class)
fun workWithPointers() {
    memScoped {
        val point1 = alloc<Point>()
        point1.x = 0.0
        point1.y = 0.0
        
        val point2 = alloc<Point>()
        point2.x = 3.0
        point2.y = 4.0
        
        // 传递结构体指针给 C 函数
        val dist = distance(point1.ptr, point2.ptr)
        println("Distance: $dist")  // 5.0
    }
}

结构体数组

kotlin
@OptIn(ExperimentalForeignApi::class)
fun structArray() {
    // 创建结构体数组
    val points = nativeHeap.allocArray<Point>(3)
    
    // 初始化
    points[0].apply {
        x = 1.0
        y = 2.0
    }
    
    points[1].apply {
        x = 3.0
        y = 4.0
    }
    
    points[2].apply {
        x = 5.0
        y = 6.0
    }
    
    // 遍历
    for (i in 0 until 3) {
        println("Point $i: (${points[i].x}, ${points[i].y})")
    }
    
    // 使用指针算术
    val secondPoint = points + 1
    println("Second point: (${secondPoint.pointed.x}, ${secondPoint.pointed.y})")
    
    nativeHeap.free(points)
}

包含数组的结构体

C 结构体中的固定大小数组会被展开:

c
// C 定义
struct ImageData {
    int width;
    int height;
    unsigned char pixels[1024];
};
kotlin
@OptIn(ExperimentalForeignApi::class)
fun useImageData() {
    memScoped {
        val image = alloc<ImageData>()
        image.width = 32
        image.height = 32
        
        // 访问数组元素
        for (i in 0 until 1024) {
            image.pixels[i] = (i % 256).toUByte()
        }
        
        // 读取
        val pixel = image.pixels[10]
        println("Pixel at 10: ${pixel.toInt()}")
    }
}

灵活数组成员 (Flexible Array Member)

C99 的灵活数组成员需要手动计算大小:

c
// C 定义
struct Buffer {
    size_t length;
    char data[];  // 灵活数组成员
};
kotlin
@OptIn(ExperimentalForeignApi::class)
fun flexibleArray(size: Int) {
    // 计算总大小
    val totalSize = sizeOf<Buffer>() + size
    
    val buffer = nativeHeap.alloc(totalSize.toLong(), alignOf<Buffer>())
        .reinterpret<Buffer>()
    
    buffer.length = size.toULong()
    
    // 访问 data(需要手动计算偏移)
    val dataPtr = (buffer.ptr.rawValue.toLong() + sizeOf<Buffer>()).toCPointer<ByteVar>()
    
    for (i in 0 until size) {
        dataPtr!![i] = ('A'.code + i).toByte()
    }
    
    nativeHeap.free(buffer)
}

联合体 (Union)

联合体基础

联合体的所有成员共享同一块内存:

c
// C 定义
union Data {
    int i;
    float f;
    double d;
    char str[20];
};
kotlin
@OptIn(ExperimentalForeignApi::class)
fun useUnion() {
    memScoped {
        val data = alloc<Data>()
        
        // 写入 int
        data.i = 42
        println("As int: ${data.i}")
        
        // 写入 float(覆盖之前的 int)
        data.f = 3.14f
        println("As float: ${data.f}")
        
        // ⚠️ 读取之前设置的 int 是未定义行为
        // println("Int after float: ${data.i}")  // 垃圾值
        
        // 写入字符串
        "Hello".cstr.use { str ->
            platform.posix.strncpy(data.str, str.ptr, 20u)
        }
        println("As string: ${data.str.toKString()}")
    }
}

类型安全的联合体包装

由于联合体的类型不安全,建议使用 Kotlin 封装:

kotlin
@OptIn(ExperimentalForeignApi::class)
sealed class TypedData {
    data class IntData(val value: Int) : TypedData()
    data class FloatData(val value: Float) : TypedData()
    data class StringData(val value: String) : TypedData()
    
    fun toUnion(): CPointer<Data> {
        val ptr = nativeHeap.alloc<Data>().ptr
        when (this) {
            is IntData -> ptr.pointed.i = value
            is FloatData -> ptr.pointed.f = value
            is StringData -> value.cstr.use { str ->
                platform.posix.strncpy(ptr.pointed.str, str.ptr, 20u)
            }
        }
        return ptr
    }
    
    companion object {
        fun fromUnion(ptr: CPointer<Data>, type: Type): TypedData {
            return when (type) {
                Type.INT -> IntData(ptr.pointed.i)
                Type.FLOAT -> FloatData(ptr.pointed.f)
                Type.STRING -> StringData(ptr.pointed.str.toKString())
            }
        }
    }
    
    enum class Type { INT, FLOAT, STRING }
}

常见用例:网络协议

联合体常用于解析二进制协议:

c
// C 定义
union IPAddress {
    uint32_t addr;
    uint8_t bytes[4];
};
kotlin
@OptIn(ExperimentalForeignApi::class)
fun parseIP(ipInt: UInt): String {
    memScoped {
        val ip = alloc<IPAddress>()
        ip.addr = ipInt
        
        // 访问各个字节
        return "${ip.bytes[0].toInt()}.${ip.bytes[1].toInt()}." +
               "${ip.bytes[2].toInt()}.${ip.bytes[3].toInt()}"
    }
}

fun usage() {
    val ip = parseIP(0xC0A80001u)  // 192.168.0.1
    println(ip)
}

枚举 (Enum)

C 枚举映射

C 枚举在 Kotlin 中可以作为枚举类或整数值使用:

c
// C 定义
enum Color {
    RED = 0,
    GREEN = 1,
    BLUE = 2
};

enum FileMode {
    READ = 1,
    WRITE = 2,
    EXECUTE = 4
};
kotlin
@OptIn(ExperimentalForeignApi::class)
fun useEnum() {
    // 作为枚举使用
    val color: Color = Color.RED
    println("Color: $color")
    
    // 获取整数值
    val value: Int = Color.GREEN.value
    println("Green value: $value")  // 1
    
    // 从整数创建枚举
    val blue = Color.byValue(2)
    println("Blue: $blue")
}

位标志枚举

C 中常用枚举表示位标志:

kotlin
@OptIn(ExperimentalForeignApi::class)
fun useFlags() {
    // 组合标志
    val mode = FileMode.READ.value or FileMode.WRITE.value
    
    // C: int open_file(const char* path, int mode)
    val fd = open_file("test.txt", mode)
    
    // 检查标志
    if ((mode and FileMode.WRITE.value) != 0) {
        println("File is writable")
    }
}

类型安全的标志包装

kotlin
@OptIn(ExperimentalForeignApi::class)
@JvmInline
value class FileModeFlags(val value: Int) {
    operator fun plus(other: FileModeFlags) = FileModeFlags(value or other.value)
    operator fun contains(flag: FileModeFlags) = (value and flag.value) != 0
    
    companion object {
        val READ = FileModeFlags(FileMode.READ.value)
        val WRITE = FileModeFlags(FileMode.WRITE.value)
        val EXECUTE = FileModeFlags(FileMode.EXECUTE.value)
    }
}

// 使用
val mode = FileModeFlags.READ + FileModeFlags.WRITE
if (FileModeFlags.WRITE in mode) {
    println("Writable")
}

最佳实践

实践1:使用 memScoped 管理临时结构体

kotlin
@OptIn(ExperimentalForeignApi::class)
// ✅ 好的模式
fun processPoint(x: Double, y: Double) {
    memScoped {
        val point = alloc<Point>()
        point.x = x
        point.y = y
        // 使用 point
    }  // 自动释放
}

// ❌ 避免的模式
fun badPattern(x: Double, y: Double) {
    val point = nativeHeap.alloc<Point>()
    point.x = x
    point.y = y
    // 忘记释放 -> 内存泄漏
}

实践2:封装不安全的联合体

kotlin
@OptIn(ExperimentalForeignApi::class)
class SafeUnion {
    private val data = nativeHeap.alloc<Data>()
    private var currentType: TypeTag? = null
    
    enum class TypeTag { INT, FLOAT, STRING }
    
    fun setInt(value: Int) {
        data.i = value
        currentType = TypeTag.INT
    }
    
    fun getInt(): Int? {
        return if (currentType == TypeTag.INT) data.i else null
    }
    
    fun setFloat(value: Float) {
        data.f = value
        currentType = TypeTag.FLOAT
    }
    
    fun getFloat(): Float? {
        return if (currentType == TypeTag.FLOAT) data.f else null
    }
    
    fun release() {
        nativeHeap.free(data)
    }
}

实践3:使用扩展函数简化访问

kotlin
@OptIn(ExperimentalForeignApi::class)
fun Point.distanceTo(other: Point): Double {
    val dx = x - other.x
    val dy = y - other.y
    return kotlin.math.sqrt(dx * dx + dy * dy)
}

// 使用
memScoped {
    val p1 = alloc<Point>().apply { x = 0.0; y = 0.0 }
    val p2 = alloc<Point>().apply { x = 3.0; y = 4.0 }
    
    println("Distance: ${p1.distanceTo(p2)}")  // 5.0
}

掌握结构体、联合体和枚举的正确使用方法是 C 互操作的基础。合理封装能够在保持性能的同时提供类型安全。