结构体、联合体与枚举
源: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 互操作的基础。合理封装能够在保持性能的同时提供类型安全。