理解Swift中的指针

指针基础知识

计算机是以字节为单位访问可寻址的存储器。机器级程序将存储器视为一个非常大的字节数组,称为虚拟存储器。这个存储器的每个字节都会有一个唯一的数字来标识,我们称为地址。所有这些地址的集合称为虚拟地址空间

计算机是以一组二进制序列为单元执行存储,传送或操作的,该单元称为。字的二进制位数称为字长。机器(CPU)的字长决定了虚拟地址空间的大小,字长n位,则虚拟地址空间的大小为:0 ~ 2^n-132位的字长,对应的虚拟地址空间大小为4千兆字节(4GB)。

指针类型使用的是机器的全字长,即:324字节,648字节。指针表示的是地址,地址是虚拟存储器每个字节的唯一数字标识,n位机器虚拟地址空间的最大地址标识可达 2^n-1,这个数字标识的地址需要被表示,必须用n位来存储。

Swift内存布局

使用MemoryLayout查看Swift基本数据类型的大小,对齐方式。

MemoryLayout<Int>.size //Int类型,连续内存占用为8字节
MemoryLayout<Int>.alignment // Int类型的内存对齐为8字节
///连续内存一个Int值的开始地址到下一个Int值开始地址之间的字节值
MemoryLayout<Int>.stride // Int类型的步幅为8字节

MemoryLayout<Optional<Int>>.size // 9字节,可选类型比普通类型多一个字节
MemoryLayout<Optional<Int>>.alignment //8字节
MemoryLayout<Optional<Int>>.stride //16

MemoryLayout<Float>.size // 4字节
MemoryLayout<Float>.alignment //4字节
MemoryLayout<Float>.stride //4字节

MemoryLayout<Double>.size // 8
MemoryLayout<Double>.alignment //8
MemoryLayout<Double>.stride //8

MemoryLayout<Bool>.size //1
MemoryLayout<Bool>.alignment //1
MemoryLayout<Bool>.stride //1

MemoryLayout<Int32>.size // 4
MemoryLayout<Int32>.alignment //4
MemoryLayout<Int32>.stride //4

使用MemoryLayout查看Swift枚举、结构体、类类型的大小,对齐方式。

///枚举类型
enum EmptyEnum {///空枚举
}
MemoryLayout<EmptyEnum>.size //0
MemoryLayout<EmptyEnum>.alignment //1 所有地址都能被1整除,故可存在于任何地址,
MemoryLayout<EmptyEnum>.stride //1

enum SampleEnum {
   case none
   case some(Int)
}
MemoryLayout<SampleEnum>.size //9
MemoryLayout<SampleEnum>.alignment //8
MemoryLayout<SampleEnum>.stride //16

///结构体
struct SampleStruct {
}
MemoryLayout<SampleStruct>.size //0
MemoryLayout<SampleStruct>.alignment //1 ,所有地址都能被1整除,故可存在于任何地址,
MemoryLayout<SampleStruct>.stride //1

struct SampleStruct {
    let b : Int
    let a : Bool
}
MemoryLayout<SampleStruct>.size // 9 but b与a的位置颠倒后,便会是16
MemoryLayout<SampleStruct>.alignment // 8
MemoryLayout<SampleStruct>.stride // 16

class EmptyClass {}
MemoryLayout<EmptyClass>.size//8
MemoryLayout<EmptyClass>.alignment//8
MemoryLayout<EmptyClass>.stride//8

class SampleClass {
    let b : Int = 2
    var a : Bool?
}
MemoryLayout<SampleClass>.size //8
MemoryLayout<SampleClass>.alignment//8
MemoryLayout<SampleClass>.stride//8

内存布局更详细的探索

Swift指针分类

Swift中的不可变指针类型:

Pointer BufferPointer
Typed UnsafePointer<T> UnsafeBufferPointer<T>
Raw UnsafeRawPointer UnsafeRawBufferPointer

Swift中的可变指针类型:

Pointer BufferPointer
Typed UnsafeMutablePointer<T> UnsafeMutableBufferPointer<T>
Raw UnsafeMutableRawPointer UnsafeMutableRawBufferPointer
  1. PointerBufferPointerPointer 表示指向内存中的单个值,如:IntBufferPointer表示指向内存中相同类型的多个值(集合)称为缓冲区指针,如[Int]

  2. TypedRaw:类型化的指针Typed提供了解释指针指向的字节序列的类型。而非类型化的指针Raw访问最原始的字节序列,未提供解释字节序列的类型。

  3. MutableImmutable:不可变指针,只读。可变指针,可读可写。

SwiftOC指针对比

Swift OC 说明
UnsafePointer<T> const T* 指针不可变,指向的内容可变
UnsafeMutablePointer<T> T* 指针及指向内容均可变
UnsafeRawPointer const void* 指向未知类型的常量指针
UnsafeMutableRawPointer void* 指向未知类型的指针

使用非类型化(Raw)的指针

示例:

使用非类型化的指针访问内存并写入三个整数(Int)。

let count = 3
let stride = MemoryLayout<Int>.stride ///8个字节,每个实例的存储空间
let byteCount = stride * count
let alignment = MemoryLayout<Int>.alignment
///通过指定的字节大小和对齐方式申请未初始化的内存
let mutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
defer {
    mutableRawPointer.deallocate()
}
/// 初始化内存
mutableRawPointer.initializeMemory(as: Int.self, repeating: 0, count: byteCount)
///RawPointer 起始位置存储 整数15
mutableRawPointer.storeBytes(of: 0xFFFFFFFFF, as: Int.self)
///将RawPointer 起始位置 偏移一个整数的位置(8个字节) 并在此位置存储一个整数 有以下两种方式:
(mutableRawPointer + stride).storeBytes(of: 0xDDDDDDD, as: Int.self)
mutableRawPointer.advanced(by: stride*2).storeBytes(of: 9, as: Int.self)
///通过`load` 查看内存中特定偏移地址的值
mutableRawPointer.load(fromByteOffset: 0, as: Int.self) // 15
mutableRawPointer.load(fromByteOffset: stride, as: Int.self) //9
///通过`UnsafeRawBufferPointer`以字节集合的形式访问内存
let buffer = UnsafeRawBufferPointer.init(start: mutableRawPointer, count: byteCount)
for (index,byte) in buffer.enumerated(){///遍历每个字节的值
    print("index:\(index),value:\(byte)")
}
//输出
//index:0,value:255  index:8,value:221  index:16,value:9
//index:1,value:255  index:9,value:221  index:17,value:0
//index:2,value:255  index:10,value:221 index:18,value:0
//index:3,value:255  index:11,value:13  index:19,value:0
//index:4,value:15   index:12,value:0   index:20,value:0
//index:5,value:0    index:13,value:0   index:21,value:0
//index:6,value:0    index:14,value:0   index:22,value:0
//index:7,value:0    index:15,value:0   index:23,value:0

UnsafeRawBufferPointer 表示内存区域中字节的集合,可用来以字节的形式访问内存。

使用类型化(Typed)的指针

针对上述示例,采用类型化指针实现:

let count = 3
let stride = MemoryLayout<Int>.stride ///8个字节,每个实例的存储空间
let byteCount = stride * count
let alignment = MemoryLayout<Int>.alignment
///申请未初始化的内存用于存储特定数目的指定类型的实例。
let mutableTypedPointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
/// 初始化内存
mutableTypedPointer.initialize(repeating: 0, count: count)
defer {
    mutableTypedPointer.deinitialize(count: count)
    mutableTypedPointer.deallocate()
}
/// 存储第一个值
mutableTypedPointer.pointee = 15
/// 存储第二个值
///`successor`返回指向下一个连续实例的指针
mutableTypedPointer.successor().pointee = 10
mutableTypedPointer[1] = 10
/// 存储第三个值
(mutableTypedPointer + 2).pointee = 20
mutableTypedPointer.advanced(by: 2).pointee = 30
/// 从内存中读取
mutableTypedPointer.pointee //15
(mutableTypedPointer + 2).pointee // 30
(mutableTypedPointer + 2).predecessor().pointee // 10
///通过`UnsafeRawBufferPointer`以字节集合的形式访问内存
let buffer = UnsafeRawBufferPointer.init(start: mutableTypedPointer, count: byteCount)
for (index,byte) in buffer.enumerated(){///遍历每个字节的值
    print("index:\(index),value:\(byte)")
}
//输出
//index:0,value:15 index:8,value:10 index:16,value:30
//index:1,value:0  index:9,value:0  index:17,value:0
//index:2,value:0  index:10,value:0 index:18,value:0
//index:3,value:0  index:11,value:0 index:19,value:0
//index:4,value:0  index:12,value:0 index:20,value:0
//index:5,value:0  index:13,value:0 index:21,value:0
//index:6,value:0  index:14,value:0 index:22,value:0
//index:7,value:0  index:15,value:0 index:23,value:0

RawPointer转换为TypedPointer

///未类型化指针申请未初始化的内存,以字节为单位
let  mutableRawPointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
defer {
    mutableRawPointer.deallocate()
}
/// Raw 转化 Typed,以对应类型的字节数 为单位
let mutableTypedPointer = mutableRawPointer.bindMemory(to: Int.self, capacity: count)
/// 初始化,非必需
mutableTypedPointer.initialize(repeating: 0, count: count)
defer {
    ///只需反初始化即可,第一个`defer`会处理指针的`deallocate`
    mutableTypedPointer.deinitialize(count: count)
}
///存储
mutableTypedPointer.pointee = 10
mutableTypedPointer.successor().pointee = 40
(mutableTypedPointer + 1).pointee = 20
mutableTypedPointer.advanced(by: 2).pointee = 30

///遍历,使用类型化的`BufferPointer`遍历
let buffer = UnsafeBufferPointer.init(start: mutableTypedPointer, count: count)
for item in buffer.enumerated() {
    print("index:\(item.0),value:\(item.1)")
}
//输出
//index:0,value:10
//index:1,value:20
//index:2,value:30

UnsafeBufferPointer<Element> 表示连续存储在内存中的Element集合,可用来以元素的形式访问内存。比如:Element的真实类型为Int16时,将会访问到内存中存储的Int16的值。

调用以指针作为参数的函数

调用以指针作为参数的函数时,可以通过隐式转换来传递兼容的指针类型,或者通过隐式桥接来传递指向变量的指针或指向数组的指针。

常量指针作为参数

当调用一个函数,它携带的指针参数为UnsafePointer<Type>时,我们可以传递的参数有:

  • UnsafePointer<Type>,UnsafeMutablePointer<Type>AutoreleasingUnsafeMutablePointer<Type>, 根据需要会隐式转换为 UnsafePointer<Type>

  • 如果TypeInt8UInt8,可以传String的实例;字符串会自动转换为UTF8,并将指向该UTF8缓冲区的指针传递给函数

  • Type类型的可变的变量,属性或下标,通过在左侧添加取地址符&的形式传递给函数。(隐式桥接)

  • 一个Type类型的数组([Type]),会以指向数组开头的指针传递给函数。(隐式桥接)

示例如下:

///定义一个接收`UnsafePointer<Int8>`作为参数的函数
func functionWithConstTypePointer(_ p: UnsafePointer<Int8>) {
    //...
}
///传递`UnsafeMutablePointer<Type>`作为参数
let mutableTypePointer = UnsafeMutablePointer<Int8>.allocate(capacity: 1)
mutableTypePointer.initialize(repeating: 10, count: 1)
defer {
    mutableTypePointer.deinitialize(count: 1)
    mutableTypePointer.deallocate()
}
functionWithConstTypePointer(mutableTypePointer)
///传递`String`作为参数
let str = "abcd"
functionWithConstTypePointer(str)
///传递输入输出型变量作为参数
var a : Int8 = 3
functionWithConstTypePointer(&a)
///传递`[Type]`数组
functionWithConstTypePointer([1,2,3,4])

当调用一个函数,它携带的指针参数为UnsafeRawPointer时,可以传递与UnsafePointer<Type>相同的参数,只不过没有了类型的限制:

示例如下:

///定义一个接收`UnsafeRawPointer`作为参数的函数
func functionWithConstRawPointer(_ p: UnsafeRawPointer) {
    //...
}
///传递`UnsafeMutablePointer<Type>`作为参数
let mutableTypePointer = UnsafeMutablePointer<Int8>.allocate(capacity: 1)
mutableTypePointer.initialize(repeating: 10, count: 1)
defer {
    mutableTypePointer.deinitialize(count: 1)
    mutableTypePointer.deallocate()
}
functionWithConstRawPointer(mutableTypePointer)
///传递`String`作为参数
let str = "abcd"
functionWithConstRawPointer(str)
///传递输入输出型变量作为参数
var a = 3.0
functionWithConstRawPointer(&a)
///传递任意类型数组
functionWithConstRawPointer([1,2,3,4] as [Int8])
functionWithConstRawPointer([1,2,3,4] as [Int16])
functionWithConstRawPointer([1.0,2.0,3.0,4.0] as [Float])

可变指针作为参数

当调用一个函数,它携带的指针参数为UnsafeMutablePointer<Type>时,我们可以传递的参数有:

  • UnsafeMutablePointer<Type>的值。
  • Type类型的可变的变量,属性或下标,通过在左侧添加取地址符&的形式传递给函数。(隐式桥接)
  • 一个Type类型的可变数组([Type]),会以指向数组开头的指针传递给函数。(隐式桥接)
    示例如下:
///定义一个接收`UnsafeMutablePointer<Int8>`作为参数的函数
func functionWithMutableTypePointer(_ p: UnsafePointer<Int8>) {
    //...
}
///传递`UnsafeMutablePointer<Type>`作为参数
let mutableTypePointer = UnsafeMutablePointer<Int8>.allocate(capacity: 1)
mutableTypePointer.initialize(repeating: 10, count: 1)
defer {
    mutableTypePointer.deinitialize(count: 1)
    mutableTypePointer.deallocate()
}
functionWithMutableTypePointer(mutableTypePointer)
///传递`Type`类型的变量
var b : Int8 = 10
functionWithMutableTypePointer(&b)
///传递`[Type]`类型的变量
var c : [Int8] = [20,10,30,40]
functionWithMutableTypePointer(&c)

同样的,当调用一个函数,它携带的指针参数为UnsafeMutableRawPointer时,可以传递与UnsafeMutablePointer<Type>相同的参数,只不过没有了类型的限制。
示例如下:

///定义一个接收`UnsafeMutableRawPointer`作为参数的函数
func functionWithMutableRawPointer(_ p: UnsafeMutableRawPointer) {
    //...
}
///传递`UnsafeMutablePointer<Type>`作为参数
let mutableTypePointer = UnsafeMutablePointer<Int8>.allocate(capacity: 1)
mutableTypePointer.initialize(repeating: 10, count: 1)
defer {
    mutableTypePointer.deinitialize(count: 1)
    mutableTypePointer.deallocate()
}
functionWithMutableRawPointer(mutableTypePointer)
///传递任意类型的变量
var b : Int8 = 10
functionWithMutableRawPointer(&b)
var d : Float = 12.0
functionWithMutableRawPointer(&d)
///传递任意类型的可变数组
var c : [Int8] = [20,10,30,40]
functionWithMutableRawPointer(&c)
var e : [Float] = [20.0,10.0,30.0,40.0]
functionWithMutableRawPointer(&e)

重要的知识点

The pointer created through implicit bridging of an instance or of an array’s elements is only valid during the execution of the called function. Escaping the pointer to use after the execution of the function is undefined behavior. In particular, do not use implicit bridging when calling an UnsafePointer/UnsafeMutablePointer/UnsafeRawPointer/UnsafeMutableRawPointerinitializer.

var number = 5
let numberPointer = UnsafePointer<Int>(&number)
// Accessing 'numberPointer' is undefined behavior.
var number = 5
let numberPointer = UnsafeMutablePointer<Int>(&number)
// Accessing 'numberPointer' is undefined behavior.
var number = 5
let numberPointer = UnsafeRawPointer(&number)
// Accessing 'numberPointer' is undefined behavior.
var number = 5
let numberPointer = UnsafeMutableRawPointer(&number)
// Accessing 'numberPointer' is undefined behavior.

what is undefined behavior?

Undefined behavior in programming languages can introduce difficult to diagnose bugs and even lead to security vulnerabilities in your App.Detail
示例:

func functionWithPointer(_ p: UnsafePointer<Int>) {
    let mPointer = UnsafeMutablePointer<Int>.init(mutating: p)
    mPointer.pointee = 6;
}
var number = 5
let numberPointer = UnsafePointer<Int>(&number)
functionWithPointer(numberPointer)
print(numberPointer.pointee) //6
print(number)//6 会出现比较难以盘查的bug
number = 7
print(numberPointer.pointee) //7
print(number)//7

内存访问

Swift中任意类型的值,Swift提供了全局函数直接访问它们的指针或内存中的字节。

访问指针

通过下列函数,Swift可以访问任意值的指针。

withUnsafePointer(to:_:)///只访问,不修改
withUnsafeMutablePointer(to:_:)//可访问,可修改

示例:

/// 只访问
let temp : [Int8] = [1,2,3,4] 
withUnsafePointer(to: temp) { point in
    print(point.pointee)
}
///规范方式:访问&修改
var temp : [Int8] = [1,2,3,4]
withUnsafeMutablePointer(to: &temp) { mPointer in
    mPointer.pointee = [6,5,4];
}
print(temp) ///[6, 5, 4]

Swift的值,如果需要通过指针的方式改变,则必须为变量,并且调用上述方法时必须将变量标记为inout参数,即变量左侧添加&。常量值,不能以inout参数的形式访问指针。

错误示例一:

///错误方式:访问&修改
var temp : [Int8] = [1,2,3,4]
withUnsafePointer(to: &temp) { point in
    let mPointer = UnsafeMutablePointer<[Int8]>.init(mutating: point)
    mPointer.pointee = [6,5,4];
}

原因如下:

A closure that takes a pointer to value as its sole argument. If the closure has a return value, that value is also used as the return value of the withUnsafePointer(to:_:) function. The pointer argument is valid only for the duration of the function’s execution. It is undefined behavior to try to mutate through the pointer argument by converting it to UnsafeMutablePointer or any other mutable pointer type. If you need to mutate the argument through the pointer, use withUnsafeMutablePointer(to:_:) instead.

错误示例二:

var tmp : [Int8] = [1,2,3,4]
///错误1
let pointer = withUnsafePointer(to: &tmp, {$0})
let mPointer = UnsafeMutablePointer<[Int8]>.init(mutating: pointer)
mPointer.pointee = [7,8,9]
///错误2
let mutablePointer = withUnsafeMutablePointer(to: &tmp) {$0}
mutablePointer.pointee = [6,5,4,3]

原因如下:

The pointer argument to body is valid only during the execution of withUnsafePointer(to:_:) or withUnsafeMutablePointer(to:_:). Do not store or return the pointer for later use.

使用上述方法访问内存时,切记不要返回或者存储闭包参数body中的指针,供以后使用。

访问字节

通过下列函数,Swift可以访问任意值的在内存中的字节。

withUnsafeBytes(of:_:)///只访问,不修改
withUnsafeMutableBytes(of:_:)///可访问,可修改

示例:

///只访问,不修改
var temp : UInt32 = UInt32.max
withUnsafeBytes(of: temp) { rawBufferPointer in
    for item in rawBufferPointer.enumerated() {
        print("index:\(item.0),value:\(item.1)")
    }
}
//输出
index:0,value:255
index:1,value:255
index:2,value:255
index:3,value:255

///规范方式:访问&修改,
var temp : UInt32 = UInt32.max //4294967295
withUnsafeMutableBytes(of: &temp) { mutableRawBuffer in
    mutableRawBuffer[1] = 0;
    mutableRawBuffer[2] = 0;
    mutableRawBuffer[3] = 0;
    //mutableRawBuffer[5] = 0;/// crash
}
print(temp) ///255

错误示例:

///访问&修改,此方式有风险,需要重点规避
var temp : UInt32 = UInt32.max //4294967295
withUnsafeBytes(of: &temp) { rawBufferPointer in
    let mutableRawBuffer = UnsafeMutableRawBufferPointer.init(mutating: rawBufferPointer)
    ///小端序
    mutableRawBuffer[1] = 0;
    mutableRawBuffer[2] = 0;
    mutableRawBuffer[3] = 0;
    for item in mutableRawBuffer.enumerated() {
        print("index:\(item.0),value:\(item.1)")
    }
}
print(temp) ///255

原因如下:

A closure that takes a raw buffer pointer to the bytes of value as its sole argument. If the closure has a return value, that value is also used as the return value of the withUnsafeBytes(of:_:) function. The buffer pointer argument is valid only for the duration of the closure’s execution. It is undefined behavior to attempt to mutate through the pointer by conversion to UnsafeMutableRawBufferPointer or any other mutable pointer type. If you want to mutate a value by writing through a pointer, use withUnsafeMutableBytes(of:_:) instead.

参考资料

https://developer.apple.com/documentation/swift/swift_standard_library/manual_memory_management/calling_functions_with_pointer_parameters

https://www.raywenderlich.com/7181017-unsafe-swift-using-pointers-and-interacting-with-c#toc-anchor-009

https://www.vadimbulavin.com/swift-pointers-overview-unsafe-buffer-raw-and-managed-pointers/

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容