[Swift] 指針UnsafePointer

本文系學(xué)習(xí)Swift中的指針操作詳解的整理

默認(rèn)情況下Swift是內(nèi)存安全的,蘋果官方不鼓勵(lì)我們直接操作內(nèi)存况既。但是罪针,Swift中也提供了使用指針操作內(nèi)存的方法啤它,直接操作內(nèi)存是很危險(xiǎn)的行為普办,很容易就出現(xiàn)錯(cuò)誤工扎,因此官方將直接操作內(nèi)存稱為 “unsafe 特性”。

在操作指針之前衔蹲,需要理解幾個(gè)概念:size肢娘、alignment、stride踪危,以及他們的獲取/使用蔬浙,這就用到了 MemoryLayout :

MemoryLayout

使用MemoryLayout,可以檢測(cè)某個(gè)類型的實(shí)際大姓暝丁(size)畴博,內(nèi)存對(duì)齊大小(alignment)蓝仲,以及實(shí)際占用的內(nèi)存大芯悴 (步長:stride),其單位均為字節(jié)袱结;

public enum MemoryLayout<T> {

    public static var size: Int { get }

    public static var stride: Int { get }

    public static var alignment: Int { get }

    public static func size(ofValue value: T) -> Int

    public static func stride(ofValue value: T) -> Int

    public static func alignment(ofValue value: T) -> Int
}

例如:如果一個(gè)類型的大辛料丁(size)為5字節(jié),對(duì)齊內(nèi)存(alignment)大小為4字節(jié)垢夹,那么其實(shí)際占用的內(nèi)存大幸缥恰(stride)為8字節(jié)果元,這是因?yàn)榫幾g需要為其填充空白的邊界促王,使其符合它的 4 字節(jié)內(nèi)存邊界對(duì)齊倡怎。

常見基本類型的內(nèi)存size迅耘、alignment、stride:

MemoryLayout<Int>.size // return 8 (on 64-bit)
MemoryLayout<Int>.alignment // return 8 (on 64-bit)
MemoryLayout<Int>.stride // return 8 (on 64-bit)

MemoryLayout<Int16>.size // return 2
MemoryLayout<Int16>.alignment // return 2
MemoryLayout<Int16>.stride // return 2

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

MemoryLayout<Float>.size // return 4
MemoryLayout<Float>.alignment // return 4
MemoryLayout<Float>.stride // return 4

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

原文中Bool的相關(guān)值為2监署,在Playground中測(cè)試結(jié)果為1颤专,所以這里更改為了1

一般在移動(dòng)指針的時(shí)候,對(duì)于特定類型钠乏,指針一次移動(dòng)一個(gè)stride(步長)血公,移動(dòng)的范圍,要在分配的內(nèi)存范圍內(nèi)缓熟,切記超出分配的內(nèi)存空間累魔,切記超出分配的內(nèi)存空間,切記超出分配的內(nèi)存空間够滑。
一般情況下stride是alignment的整數(shù)倍垦写,即符合內(nèi)存對(duì)齊原則;實(shí)際分配的內(nèi)存空間大小也是alignment的整數(shù)倍彰触,但是實(shí)際實(shí)例大小可能會(huì)小于實(shí)際分配的內(nèi)存空間大小梯投。

UnsafePointer

所有指針類型為 UnsafePointer,一旦你操作了內(nèi)存况毅,編譯器不會(huì)對(duì)這種操作進(jìn)行檢測(cè)分蓖,你需要對(duì)自己的代碼承擔(dān)全部的責(zé)任。Swift中定義了一些特定類型的指針尔许,每個(gè)類型都有他們的作用和目的么鹤,使用適當(dāng)?shù)闹羔橆愋涂梢苑乐瑰e(cuò)誤的發(fā)生,并且更清晰得表達(dá)開發(fā)者的意圖味廊,防止未定義行為的產(chǎn)生蒸甜。

通過指針類型的名稱,我們可以知道這是一個(gè)什么類型的指針:可變/不可變余佛、原生(raw)/有類型柠新、是否是緩沖類型(buffer),大致有以下8種類型:

Pointer Name Unsafe辉巡? Write Access恨憎? Collection Strideable? Typed郊楣?
UnsafeMutablePointer<T> yes yes no yes yes
UnsafePointer<T> yes no no yes yes
UnsafeMutableBufferPointer<T> yes yes yes no yes
UnsafeBufferPointer<T> yes no yes no yes
UnsafeRawPointer yes no no yes no
UnsafeMutableRawPointer yes yes no yes no
UnsafeMutableRawBufferPointer yes yes yes no no
UnsafeRawBufferPointer yes no yes no no
  • unsafe:不安全的
  • Write Access:可寫入
  • Collection:像一個(gè)容器憔恳,可添加數(shù)據(jù)
  • Strideable:指針可使用 advanced 函數(shù)移動(dòng)
  • Typed:是否需要指定類型(范型)

原生(Raw)指針

// 1
let count = 2
let stride = MemoryLayout<Int>.stride
let aligment = MemoryLayout<Int>.alignment
let byteCount = stride * count

// 2
do {
    print("raw pointers")
    // 3
    let pointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: aligment)
    
    // 4
    defer {
        pointer.deallocate()
        
    }
    
    // 5
    pointer.storeBytes(of: 42, as: Int.self)
    pointer.advanced(by: stride).storeBytes(of: 6, as: Int.self)
// 讀取第一個(gè)值
    pointer.load(as: Int.self)
// 讀取第二個(gè)值
    pointer.advanced(by: stride).load(as: Int.self)
    
    // 6
    let bufferPointer = UnsafeRawBufferPointer(start: pointer, count: byteCount)
    for (index, byte) in bufferPointer.enumerated() {
        print("bute \(index): \(byte)")
    }
}

代碼說明:

    1. 聲明示例用到的參數(shù)
      count :整數(shù)的個(gè)數(shù)
      stride:整數(shù)的步長
      aligment:整數(shù)的內(nèi)存對(duì)齊大小
      byteCount:實(shí)際需要的內(nèi)存大小
    1. 聲明作用域
      使用 do 來增加一個(gè)作用域,讓我們可以在接下的示例中復(fù)用作用域中的變量名痢甘。
    1. UnsafeMutableRawPointer.allocate 創(chuàng)建分配所需字節(jié)數(shù)喇嘱,該指針可以用來讀取和存儲(chǔ)(改變)原生的字節(jié)。

這里分配內(nèi)存使用了allocate 方法:

public static func allocate(byteCount: Int, alignment: Int) -> UnsafeMutableRawPointer
  • byteCount:所需字節(jié)數(shù)
  • alignment:內(nèi)存對(duì)齊
    1. 延時(shí)釋放
      使用 defer 來保證內(nèi)存得到正確地釋放塞栅,操作指針的時(shí)候者铜,所有內(nèi)存都需要我們手動(dòng)進(jìn)行管理。
      這里釋放內(nèi)存使用了deallocate方法:
 public func deallocate()

allocate 和 deallocate 方法一定要配對(duì)出現(xiàn)放椰。

    1. 使用 storeBytes 和 load 方法存儲(chǔ)和讀取字節(jié)

存儲(chǔ)數(shù)據(jù)方法storeBytes:

/// - Parameters:
///   - value: The value to store as raw bytes.
///   - offset: The offset from this pointer, in bytes. `offset` must be
///     nonnegative. The default is zero.
///   - type: The type of `value`.
public func storeBytes<T>(of value: T, toByteOffset offset: Int = default, as type: T.Type)
  • value:要存儲(chǔ)的值
  • offset:偏移量作烟,默認(rèn)即可
  • type:值的類型

讀取數(shù)據(jù)方法 load :

/// - Parameters:
    ///   - offset: The offset from this pointer, in bytes. `offset` must be
    ///     nonnegative. The default is zero.
    ///   - type: The type of the instance to create.
    /// - Returns: A new instance of type `T`, read from the raw bytes at
    ///   `offset`. The returned instance is memory-managed and unassociated
    ///   with the value in the memory referenced by this pointer.
    public func load<T>(fromByteOffset offset: Int = default, as type: T.Type) -> T
  • offset:偏移量,默認(rèn)即可
  • type:值的類型

移動(dòng)指針地址 advanced :

/// - Parameter n: The number of bytes to offset this pointer. `n` may be
    ///   positive, negative, or zero.
    /// - Returns: A pointer offset from this pointer by `n` bytes.
    public func advanced(by n: Int) -> UnsafeMutableRawPointer
  • n:步長stride

使用原生指針砾医,存儲(chǔ)下一個(gè)值的時(shí)候需要移動(dòng)一個(gè)步長(stride)拿撩,也可以直接使用 + 運(yùn)算符:

(pointer + stride).storeBytes(of: 6, as: Int.self)
    1. UnsafeRawBufferPointer 類型以字節(jié)流的形式來讀取內(nèi)存。這意味著我們可以這些字節(jié)進(jìn)行迭代如蚜,對(duì)其使用下標(biāo)压恒,或者使用 filter影暴,map 以及 reduce 這些很酷的方法,緩沖類型指針使用了原生指針進(jìn)行初始化探赫。

類型指針

do {
    print("Typed pointers")
    // 1.
    let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
    pointer.initialize(repeating: 0, count: count)

    // 2.
    defer {
        pointer.deinitialize(count: count)
        pointer.deallocate()
    }
    // 3.
    pointer.pointee = 42
    pointer.advanced(by: 1).pointee = 6
    pointer.pointee
    pointer.advanced(by: 1).pointee
    
    let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
    for (index, value) in bufferPointer.enumerated() {
        print("value \(index): \(value)")
    }
    
}

類型指針與原生指針的區(qū)別型宙,主要體現(xiàn)在上面標(biāo)注數(shù)字的幾個(gè)地方:

    1. 分配內(nèi)存、初始化

類型指針伦吠,在分配內(nèi)存的時(shí)候通過給范型賦值來指定當(dāng)前指針?biāo)僮鞯臄?shù)據(jù)類型:

/// - Parameter count: The amount of memory to allocate, counted in instances
    ///   of `Pointee`.
    public static func allocate(capacity count: Int) -> UnsafeMutablePointer<Pointee>
  • count:要存儲(chǔ)的數(shù)據(jù)個(gè)數(shù)

可以看到其分配內(nèi)存的方法妆兑,只有一個(gè)參數(shù),指定所要存儲(chǔ)的數(shù)據(jù)個(gè)數(shù)即可毛仪,因?yàn)橥ㄟ^給范型參數(shù)賦值搁嗓,已經(jīng)知道了要存儲(chǔ)的數(shù)據(jù)類型,其alignment和stride就確定了箱靴,這時(shí)只需要再知道存儲(chǔ)幾個(gè)數(shù)據(jù)即可腺逛。

這里還多了個(gè)初始化的過程,類型指針單單分配內(nèi)存刨晴,還不能使用屉来,還需要初始化:

/// - Parameters:
    ///   - repeatedValue: The instance to initialize this pointer's memory with.
    ///   - count: The number of consecutive copies of `newValue` to initialize.
    ///     `count` must not be negative. 
    public func initialize(repeating repeatedValue: Pointee, count: Int)
  • repeatedValue:默認(rèn)值
  • count:數(shù)量
    1. 延時(shí)釋放

在釋放的時(shí)候,要先釋放已初始化的實(shí)例(deinitialize)狈癞,再釋放已分配的內(nèi)存(deallocate)空間:

/// - Parameter count: The number of instances to deinitialize. `count` must
    ///   not be negative. 
    /// - Returns: A raw pointer to the same address as this pointer. The memory
    ///   referenced by the returned raw pointer is still bound to `Pointee`.
    public func deinitialize(count: Int) -> UnsafeMutableRawPointer
  • count:數(shù)量
    1. 存儲(chǔ)/讀取
      類型指針的存儲(chǔ)/讀取值茄靠,不需要再使用storeBytes/load,Swift提供了一個(gè)以類型安全的方式讀取和存儲(chǔ)值--pointee:
public var pointee: Pointee { get nonmutating set }

這里的移動(dòng)指針的方法蝶桶,和上面的一致慨绳,也是 advanced ,但是其參數(shù)有所不同:

/// - Parameter n: The number of strides of the pointer's `Pointee` type to
    ///   offset this pointer. To access the stride, use
    ///   `MemoryLayout<Pointee>.stride`. `n` may be positive, negative, or
    ///   zero.
    /// - Returns: A pointer offset from this pointer by `n` instances of the
    ///   `Pointee` type.
    public func advanced(by n: Int) -> UnsafeMutablePointer<Pointee>
  • n:這里是按類型值的個(gè)數(shù)進(jìn)行移動(dòng)

同樣真竖,這里也可以使用運(yùn)算符 + 進(jìn)行移動(dòng):

(pointer + 1).pointee = 6

原生指針轉(zhuǎn)換為類型指針

do {
    print("Converting raw pointers to typed pointers")
    // 創(chuàng)建原生指針
    let rawPointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: aligment)

// 延遲釋放原生指針的內(nèi)存
    defer {
        rawPointer.deallocate()
    }
    // 將原生指針綁定類型
    let typePointer = rawPointer.bindMemory(to: Int.self, capacity: count)
    typePointer.initialize(repeating: 0, count: count)
    defer {
        typePointer.deinitialize(count: count)
    }
    
    typePointer.pointee = 42
    typePointer.advanced(by: 1).pointee = 9
    typePointer.pointee
    typePointer.advanced(by: 1).pointee
    
    let bufferPointer = UnsafeBufferPointer(start: typePointer, count: count)
    for (index, value) in bufferPointer.enumerated() {
        print("value \(index): \(value)")
    }
}

原生指針轉(zhuǎn)換為類型指針脐雪,是通過調(diào)用內(nèi)存綁定到特定的類型來完成的:

/// - Parameters:
    ///   - type: The type `T` to bind the memory to.
    ///   - count: The amount of memory to bind to type `T`, counted as instances
    ///     of `T`.
    /// - Returns: A typed pointer to the newly bound memory. The memory in this
    ///   region is bound to `T`, but has not been modified in any other way.
    ///   The number of bytes in this region is
    ///   `count * MemoryLayout<T>.stride`.
    public func bindMemory<T>(to type: T.Type, capacity count: Int) -> UnsafeMutablePointer<T>
  • type:數(shù)據(jù)類型
  • count:容量

通過對(duì)內(nèi)存的綁定,我們可以通過類型安全的方法來訪問它恢共。其實(shí)我們手動(dòng)創(chuàng)建類型指針的時(shí)候战秋,系統(tǒng)自動(dòng)幫我們進(jìn)行了內(nèi)存綁定。

獲取一個(gè)實(shí)例的字節(jié)

這里定義了一個(gè)結(jié)構(gòu)體 Sample來作為示例:

struct Sample {
    
    var number: Int
    var flag: Bool
    
    init(number: Int, flag: Bool) {
        self.number = number
        self.flag = flag
    }
}

do {
    print("Getting the bytes of an instance")
    
    var sample = Sample(number: 25, flag: true)
    // 1.
    withUnsafeBytes(of: &sample) { (rs) in
        
        for bute in rs {
            print(bute)
        }
    }
}

這里主要是使用了withUnsafeBytes 方法來實(shí)現(xiàn)獲取字節(jié)數(shù):

/// - Parameters:
///   - arg: An instance to temporarily access through a raw buffer pointer.
///   - body: A closure that takes a raw buffer pointer to the bytes of `arg`
///     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.
/// - Returns: The return value, if any, of the `body` closure.
public func withUnsafeBytes<T, Result>(of arg: inout T, _ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result
  • arg:實(shí)例對(duì)象地址
  • body:回調(diào)閉包讨韭,參數(shù)為UnsafeRawBufferPointer 類型的指針

注意:該方法和回調(diào)閉包都有返回值脂信,如果閉包有返回值,此返回值將會(huì)作為該方法的返回值透硝;但是狰闪,一定不要在閉包中將body的參數(shù),即:UnsafeRawBufferPointer 類型的指針作為返回值返回濒生,該參數(shù)的使用范圍僅限當(dāng)前閉包埋泵,該參數(shù)的使用范圍僅限當(dāng)前閉包,該參數(shù)的使用范圍僅限當(dāng)前閉包。

withUnsafeBytes 同樣適合用 Array 和 Data 的實(shí)例.

使用指針的原則

不要從 withUnsafeBytes 中返回指針

絕對(duì)不要讓指針逃出 withUnsafeBytes(of:) 的作用域范圍丽声。這樣的代碼會(huì)成為定時(shí)炸彈礁蔗,你永遠(yuǎn)不知道它什么時(shí)候可以用,而什么時(shí)候會(huì)崩潰雁社。

一次只綁定一種類型

在使用 bindMemory方法將原生指針綁定內(nèi)存類型瘦麸,轉(zhuǎn)為類型指針的時(shí)候,一次只能綁定一個(gè)類型歧胁,例如:將一個(gè)原生指針綁定Int類型,不能再綁定Bool類型:

let typePointer = rawPointer.bindMemory(to: Int.self, capacity: count)
// 一定不要這么做
let typePointer1 = rawPointer.bindMemory(to: Bool.self, capacity: count)

但是厉碟,我們可以使用 withMemoryRebound 來對(duì)內(nèi)存進(jìn)行重新綁定喊巍。并且,這條規(guī)則也表明了不要將一個(gè)基本類型(如 Int)重新綁定到一個(gè)自定義類型(如 class)上驶冒。

/// - Parameters:
    ///   - type: The type to temporarily bind the memory referenced by this
    ///     pointer. The type `T` must be the same size and be layout compatible
    ///     with the pointer's `Pointee` type.
    ///   - count: The number of instances of `T` to bind to `type`.
    ///   - body: A closure that takes a mutable typed pointer to the
    ///     same memory as this pointer, only bound to type `T`. The closure's
    ///     pointer argument is valid only for the duration of the closure's
    ///     execution. If `body` has a return value, that value is also used as
    ///     the return value for the `withMemoryRebound(to:capacity:_:)` method.
    /// - Returns: The return value, if any, of the `body` closure parameter.
    public func withMemoryRebound<T, Result>(to type: T.Type, capacity count: Int, _ body: (UnsafeMutablePointer<T>) throws -> Result) rethrows -> Result
  • type:值的類型
  • count:值的個(gè)數(shù)
  • body:回調(diào)閉包扶认,參數(shù)為UnsafeRawBufferPointer 類型的指針

注意:該方法和回調(diào)閉包都有返回值声诸,如果閉包有返回值,此返回值將會(huì)作為該方法的返回值何暮;但是,一定不要在閉包中將body的參數(shù)铐殃,即:UnsafeRawBufferPointer 類型的指針作為返回值返回海洼,該參數(shù)的使用范圍僅限當(dāng)前閉包,該參數(shù)的使用范圍僅限當(dāng)前閉包富腊,該參數(shù)的使用范圍僅限當(dāng)前閉包坏逢。

不要操作超出范圍的內(nèi)存
do {
    
    let count = 3
    let stride = MemoryLayout<Int16>.stride
    let alignment = MemoryLayout<Int16>.alignment
    let byteCount = count * stride
    
    let pointer = UnsafeMutableRawPointer.allocate(byteCount: byteCount, alignment: alignment)
    // 1. 這里的count+1,超出了原有指針pointer分配的內(nèi)存范圍
    let bufferPointer = UnsafeRawBufferPointer.init(start: pointer, count: count + 1)
    
    for byte in bufferPointer {
        print(byte)
    }
}

這里的count+1赘被,超出了原有指針pointer分配的內(nèi)存范圍是整,切記不要出現(xiàn)這種情況。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末民假,一起剝皮案震驚了整個(gè)濱河市浮入,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌羊异,老刑警劉巖事秀,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異球化,居然都是意外死亡秽晚,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門筒愚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赴蝇,“玉大人,你說我怎么就攤上這事巢掺【淞妫” “怎么了劲蜻?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長考余。 經(jīng)常有香客問我先嬉,道長,這世上最難降的妖魔是什么楚堤? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任疫蔓,我火速辦了婚禮,結(jié)果婚禮上身冬,老公的妹妹穿的比我還像新娘衅胀。我一直安慰自己,他們只是感情好酥筝,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布滚躯。 她就那樣靜靜地躺著,像睡著了一般嘿歌。 火紅的嫁衣襯著肌膚如雪掸掏。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天宙帝,我揣著相機(jī)與錄音丧凤,去河邊找鬼。 笑死茄唐,一個(gè)胖子當(dāng)著我的面吹牛息裸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沪编,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼呼盆,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了蚁廓?” 一聲冷哼從身側(cè)響起访圃,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎相嵌,沒想到半個(gè)月后腿时,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饭宾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年批糟,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片看铆。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡徽鼎,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情否淤,我是刑警寧澤悄但,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站石抡,受9級(jí)特大地震影響檐嚣,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜啰扛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一嚎京、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧隐解,春花似錦挖藏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽岩臣。三九已至溜嗜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間架谎,已是汗流浹背炸宵。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留谷扣,地道東北人土全。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像会涎,于是被迫代替她去往敵國和親裹匙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容