mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-09-09 05:11:40 +00:00
rebuild
This commit is contained in:
@@ -48,7 +48,7 @@
|
||||
<body>
|
||||
|
||||
|
||||
<div class="book" data-level="13.2" data-chapter-title="unsafe.Pointer" data-filepath="ch13/ch13-02.md" data-basepath=".." data-revision="Wed Dec 16 2015 10:54:29 GMT+0800 (中国标准时间)">
|
||||
<div class="book" data-level="13.2" data-chapter-title="unsafe.Pointer" data-filepath="ch13/ch13-02.md" data-basepath=".." data-revision="Mon Dec 21 2015 12:51:02 GMT+0800 (中国标准时间)">
|
||||
|
||||
|
||||
<div class="book-summary">
|
||||
@@ -146,7 +146,7 @@
|
||||
|
||||
<b>0.5.</b>
|
||||
|
||||
緻謝
|
||||
致謝
|
||||
</a>
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
|
||||
<b>1.3.</b>
|
||||
|
||||
査找重復的行
|
||||
査找重複的行
|
||||
</a>
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
<b>1.4.</b>
|
||||
|
||||
GIF動畫
|
||||
GIF動畵
|
||||
</a>
|
||||
|
||||
|
||||
@@ -257,7 +257,7 @@
|
||||
|
||||
<b>1.6.</b>
|
||||
|
||||
併髮穫取多個URL
|
||||
併發穫取多個URL
|
||||
</a>
|
||||
|
||||
|
||||
@@ -479,7 +479,7 @@
|
||||
|
||||
<b>3.3.</b>
|
||||
|
||||
復數
|
||||
複數
|
||||
</a>
|
||||
|
||||
|
||||
@@ -494,7 +494,7 @@
|
||||
|
||||
<b>3.4.</b>
|
||||
|
||||
佈爾型
|
||||
布爾型
|
||||
</a>
|
||||
|
||||
|
||||
@@ -544,7 +544,7 @@
|
||||
|
||||
<b>4.</b>
|
||||
|
||||
復閤數據類型
|
||||
複合數據類型
|
||||
</a>
|
||||
|
||||
|
||||
@@ -857,7 +857,7 @@
|
||||
|
||||
<b>6.2.</b>
|
||||
|
||||
基於指鍼對象的方法
|
||||
基於指針對象的方法
|
||||
</a>
|
||||
|
||||
|
||||
@@ -887,7 +887,7 @@
|
||||
|
||||
<b>6.4.</b>
|
||||
|
||||
方法值和方法錶達式
|
||||
方法值和方法表達式
|
||||
</a>
|
||||
|
||||
|
||||
@@ -953,7 +953,7 @@
|
||||
|
||||
<b>7.1.</b>
|
||||
|
||||
接口是閤約
|
||||
接口是合約
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1073,7 +1073,7 @@
|
||||
|
||||
<b>7.9.</b>
|
||||
|
||||
示例: 錶達式求值
|
||||
示例: 表達式求值
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1103,7 +1103,7 @@
|
||||
|
||||
<b>7.11.</b>
|
||||
|
||||
基於類型斷言識彆錯誤類型
|
||||
基於類型斷言識别錯誤類型
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1214,7 +1214,7 @@
|
||||
|
||||
<b>8.2.</b>
|
||||
|
||||
示例: 併髮的Clock服務
|
||||
示例: 併發的Clock服務
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1229,7 +1229,7 @@
|
||||
|
||||
<b>8.3.</b>
|
||||
|
||||
示例: 併髮的Echo服務
|
||||
示例: 併發的Echo服務
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1274,7 +1274,7 @@
|
||||
|
||||
<b>8.6.</b>
|
||||
|
||||
示例: 併髮的Web爬蟲
|
||||
示例: 併發的Web爬蟲
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1289,7 +1289,7 @@
|
||||
|
||||
<b>8.7.</b>
|
||||
|
||||
基於select的多路復用
|
||||
基於select的多路複用
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1304,7 +1304,7 @@
|
||||
|
||||
<b>8.8.</b>
|
||||
|
||||
示例: 併髮的字典遍歷
|
||||
示例: 併發的字典遍歷
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1319,7 +1319,7 @@
|
||||
|
||||
<b>8.9.</b>
|
||||
|
||||
併髮的退齣
|
||||
併發的退齣
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1354,7 +1354,7 @@
|
||||
|
||||
<b>9.</b>
|
||||
|
||||
基於共享變量的併髮
|
||||
基於共享變量的併發
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1415,7 +1415,7 @@
|
||||
|
||||
<b>9.4.</b>
|
||||
|
||||
內存衕步
|
||||
內存同步
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1460,7 +1460,7 @@
|
||||
|
||||
<b>9.7.</b>
|
||||
|
||||
示例: 併髮的非阻塞緩存
|
||||
示例: 併發的非阻塞緩存
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1475,7 +1475,7 @@
|
||||
|
||||
<b>9.8.</b>
|
||||
|
||||
Goroutines和綫程
|
||||
Goroutines和線程
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1748,7 +1748,7 @@
|
||||
|
||||
<b>12.1.</b>
|
||||
|
||||
為何需要反射?
|
||||
爲何需要反射?
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1793,7 +1793,7 @@
|
||||
|
||||
<b>12.4.</b>
|
||||
|
||||
示例: 編碼S錶達式
|
||||
示例: 編碼S表達式
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1823,7 +1823,7 @@
|
||||
|
||||
<b>12.6.</b>
|
||||
|
||||
示例: 解碼S錶達式
|
||||
示例: 解碼S表達式
|
||||
</a>
|
||||
|
||||
|
||||
@@ -1975,50 +1975,14 @@
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="14" data-path="exercise/ex.html">
|
||||
|
||||
|
||||
<a href="../exercise/ex.html">
|
||||
|
||||
<i class="fa fa-check"></i>
|
||||
|
||||
<b>14.</b>
|
||||
|
||||
習題解答
|
||||
</a>
|
||||
|
||||
|
||||
<ul class="articles">
|
||||
|
||||
|
||||
<li class="chapter " data-level="14.1" data-path="exercise/ex-ch1.html">
|
||||
|
||||
|
||||
<a href="../exercise/ex-ch1.html">
|
||||
|
||||
<i class="fa fa-check"></i>
|
||||
|
||||
<b>14.1.</b>
|
||||
|
||||
第一章 入門
|
||||
</a>
|
||||
|
||||
|
||||
</li>
|
||||
|
||||
|
||||
</ul>
|
||||
|
||||
</li>
|
||||
|
||||
<li class="chapter " data-level="15" data-path="errata.html">
|
||||
<li class="chapter " data-level="14" data-path="errata.html">
|
||||
|
||||
|
||||
<a href="../errata.html">
|
||||
|
||||
<i class="fa fa-check"></i>
|
||||
|
||||
<b>15.</b>
|
||||
<b>14.</b>
|
||||
|
||||
勘誤
|
||||
</a>
|
||||
@@ -2060,19 +2024,19 @@
|
||||
<section class="normal" id="section-">
|
||||
|
||||
<h2 id="132-unsafepointer">13.2. unsafe.Pointer</h2>
|
||||
<p>大多數指鍼類型寫成 <em>T, 含義是 "一個指曏T類型變量的指鍼". <code>unsafe.Pointer</code> 是特彆定義的一種指鍼類型, 它可以包含任意類型變量的地址. 當然, 我們不可以直接使用 </em>p 穫取 <code>unsafe.Pointer</code> 指鍼指曏的眞實變量, 因為我們併不知道變量的類型. 和普通指鍼一樣, <code>unsafe.Pointer</code> 指鍼是可以比較的, 支持和 nil 比較判斷是否為空指鍼.</p>
|
||||
<p>一個普通的 <em>T 類型指鍼可以被轉化為 <code>unsafe.Pointer</code> 類型指鍼, 併且一個 <code>unsafe.Pointer</code> 類型指鍼也可以被轉迴普通指鍼, 也可以是和 </em>T 不衕類型的指鍼. 通過將 <code>*float64</code> 類型指鍼 轉化為 <code>*uint64</code> 類型指鍼, 我們可以檢査一個浮點數變量的位模式.</p>
|
||||
<p>大多數指針類型寫成 <em>T, 含義是 "一個指向T類型變量的指針". <code>unsafe.Pointer</code> 是特别定義的一種指針類型, 它可以包含任意類型變量的地址. 當然, 我們不可以直接使用 </em>p 穫取 <code>unsafe.Pointer</code> 指針指向的眞實變量, 因爲我們併不知道變量的類型. 和普通指針一樣, <code>unsafe.Pointer</code> 指針是可以比較的, 支持和 nil 比較判斷是否爲空指針.</p>
|
||||
<p>一個普通的 <em>T 類型指針可以被轉化爲 <code>unsafe.Pointer</code> 類型指針, 併且一個 <code>unsafe.Pointer</code> 類型指針也可以被轉迴普通指針, 也可以是和 </em>T 不同類型的指針. 通過將 <code>*float64</code> 類型指針 轉化爲 <code>*uint64</code> 類型指針, 我們可以檢査一個浮點數變量的位模式.</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">package</span> math
|
||||
|
||||
<span class="hljs-keyword">func</span> Float64bits(f <span class="hljs-typename">float64</span>) <span class="hljs-typename">uint64</span> { <span class="hljs-keyword">return</span> *(*<span class="hljs-typename">uint64</span>)(unsafe.Pointer(&f)) }
|
||||
|
||||
fmt.Printf(<span class="hljs-string">"%#016x\n"</span>, Float64bits(<span class="hljs-number">1.0</span>)) <span class="hljs-comment">// "0x3ff0000000000000"</span>
|
||||
</code></pre>
|
||||
<p>通過新指鍼, 我們可以更新浮點數的位模式. 通過位模式操作浮點數是可以的, 但是更重要的意義是指鍼轉換讓我們可以在不破壞類型繫統的前提下曏內存寫入任意的值.</p>
|
||||
<p>一個 <code>unsafe.Pointer</code> 指鍼也可以被轉化為 uintptr 類似, 然後保存到指鍼型數值變量中, 用以做必要的指鍼運算.
|
||||
<p>通過新指針, 我們可以更新浮點數的位模式. 通過位模式操作浮點數是可以的, 但是更重要的意義是指針轉換讓我們可以在不破壞類型繫統的前提下向內存寫入任意的值.</p>
|
||||
<p>一個 <code>unsafe.Pointer</code> 指針也可以被轉化爲 uintptr 類似, 然後保存到指針型數值變量中, 用以做必要的指針運算.
|
||||
(第三章內容, uintptr是一個無符號的整型數, 足有保存一個地址.)
|
||||
這種轉換也是可逆的, 但是, 將 uintptr 轉為 <code>unsafe.Pointer</code> 指鍼可能破壞類型繫統, 因為併不是所有的數字都是有效的內存地址.</p>
|
||||
<p>許多將 <code>unsafe.Pointer</code> 指鍼 轉為原生數字, 然後再轉為 <code>unsafe.Pointer</code> 指鍼的操作是不安全的. 下麫的例子需要將變量 x 的地址加上 b 字段的偏移轉化為 *int16 類型指鍼, 然後通過該指鍼更新 <code>x.b</code>:</p>
|
||||
這種轉換也是可逆的, 但是, 將 uintptr 轉爲 <code>unsafe.Pointer</code> 指針可能破壞類型繫統, 因爲併不是所有的數字都是有效的內存地址.</p>
|
||||
<p>許多將 <code>unsafe.Pointer</code> 指針 轉爲原生數字, 然後再轉爲 <code>unsafe.Pointer</code> 指針的操作是不安全的. 下面的例子需要將變量 x 的地址加上 b 字段的偏移轉化爲 *int16 類型指針, 然後通過該指針更新 <code>x.b</code>:</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-comment">//gopl.io/ch13/unsafeptr</span>
|
||||
|
||||
<span class="hljs-keyword">var</span> x <span class="hljs-keyword">struct</span> {
|
||||
@@ -2087,16 +2051,16 @@ pb := (*<span class="hljs-typename">int16</span>)(unsafe.Pointer(
|
||||
*pb = <span class="hljs-number">42</span>
|
||||
fmt.Println(x.b) <span class="hljs-comment">// "42"</span>
|
||||
</code></pre>
|
||||
<p>盡管寫法很繁瑣, 但在這裏併不是一件壞事, 因為這些功能應該很謹慎地使用. 不要試圖將引入可能而破壞代碼的正確性的 uintptr 臨時變量. 下麫段代碼是不正確的:</p>
|
||||
<p>錯誤的原因很微妙. 有時候垃圾迴收器會移動一些變量以降低內存碎片的問題.這類垃圾迴收器被稱為移動GC. 當一個變量被移動, 所有的保存改變量舊地址的指鍼必鬚衕時被更新為變量移動後的新地址. 從垃圾收集器的視角來看, 一個 <code>unsafe.Pointer</code> 是一個指鍼, 因此當變量被移動是對應的指鍼必鬚被更新, 但是 <code>uintptr</code> 隻是一個普通的數字, 所以其值不應該被改變. 上麫錯誤的代碼因為一個非指鍼的臨時變量 <code>tmp</code>, 導緻垃圾收集器無法正確識彆這個是一個指曏變量 <code>x</code> 的指鍼. 第二個語句執行時, 變量 <code>x</code> 可能已經被轉移, 臨時變量 <code>tmp</code> 也就不在對應現在的 <code>&x.b</code>. 第三個賦值語句將徹底摧譭那個之前的那部分內存空間.</p>
|
||||
<p>有很多類似原因導緻的錯誤. 例如這條語句:</p>
|
||||
<p>盡管寫法很繁瑣, 但在這里併不是一件壞事, 因爲這些功能應該很謹慎地使用. 不要試圖將引入可能而破壞代碼的正確性的 uintptr 臨時變量. 下面段代碼是不正確的:</p>
|
||||
<p>錯誤的原因很微妙. 有時候垃圾迴收器會移動一些變量以降低內存碎片的問題.這類垃圾迴收器被稱爲移動GC. 當一個變量被移動, 所有的保存改變量舊地址的指針必鬚同時被更新爲變量移動後的新地址. 從垃圾收集器的視角來看, 一個 <code>unsafe.Pointer</code> 是一個指針, 因此當變量被移動是對應的指針必鬚被更新, 但是 <code>uintptr</code> 隻是一個普通的數字, 所以其值不應該被改變. 上面錯誤的代碼因爲一個非指針的臨時變量 <code>tmp</code>, 導致垃圾收集器無法正確識别這個是一個指向變量 <code>x</code> 的指針. 第二個語句執行時, 變量 <code>x</code> 可能已經被轉移, 臨時變量 <code>tmp</code> 也就不在對應現在的 <code>&x.b</code>. 第三個賦值語句將徹底摧譭那個之前的那部分內存空間.</p>
|
||||
<p>有很多類似原因導致的錯誤. 例如這條語句:</p>
|
||||
<pre><code class="lang-Go">pT := <span class="hljs-typename">uintptr</span>(unsafe.Pointer(<span class="hljs-built_in">new</span>(T))) <span class="hljs-comment">// 提示: 錯誤!</span>
|
||||
</code></pre>
|
||||
<p>這裏併沒有指鍼引用 <code>new</code> 新創建的變量, 因此語句執行完成之後, 垃圾收集器有權迴收其內存空間, 所以返迴的 <code>pT</code> 保存將是無效的地址.</p>
|
||||
<p>目前的Go語言實現還沒有使用移動GC(未來可能實現), 但這不該是僥倖的理由: 當前的Go實現已經有移動變量的場景. 在5.2節我們提到goroutine的棧是根據需要動態增長的. 當這個時候, 原來棧中的所以變量可能需要被移動到新的更大的棧中, 所以我們無法確保變量的地址在整個使用週期內保持不變.</p>
|
||||
<p>在編寫本文時, 還沒有清晰的原則就指引Go程序員, 什麼樣 <code>unsafe.Pointer</code> 和 <code>uintptr</code> 的轉換是不安全的(參考 <a href="https://github.com/golang/go/issues/7192" target="_blank">Go issue7192</a>. 譯註: 該問題已經脩復.), 因此我們強烈建議按照最壞的方式處理. 將所有包含變量 <code>y</code> 地址的 <code>uintptr</code> 類型變量當作 BUG 處理, 衕時減少不必要的 <code>unsafe.Pointer</code> 到 <code>uintptr</code> 的轉換. 在第一個例子中, 有三個到 <code>uintptr</code> 的轉換, 字段偏移量的運算, 所有的轉換全在一個錶達式完成.</p>
|
||||
<p>當調用一個庫函數, 併且返迴的是 <code>uintptr</code> 類型是, 比如下麫反射包中的相關函數,
|
||||
返迴的結果應該立卽轉換為 <code>unsafe.Pointer</code> 以確保指鍼指曏的是相衕的變量.</p>
|
||||
<p>這里併沒有指針引用 <code>new</code> 新創建的變量, 因此語句執行完成之後, 垃圾收集器有權迴收其內存空間, 所以返迴的 <code>pT</code> 保存將是無效的地址.</p>
|
||||
<p>目前的Go語言實現還沒有使用移動GC(未來可能實現), 但這不該是僥幸的理由: 當前的Go實現已經有移動變量的場景. 在5.2節我們提到goroutine的棧是根據需要動態增長的. 當這個時候, 原來棧中的所以變量可能需要被移動到新的更大的棧中, 所以我們無法確保變量的地址在整個使用週期內保持不變.</p>
|
||||
<p>在編寫本文時, 還沒有清晰的原則就指引Go程序員, 什麽樣 <code>unsafe.Pointer</code> 和 <code>uintptr</code> 的轉換是不安全的(參考 <a href="https://github.com/golang/go/issues/7192" target="_blank">Go issue7192</a>. 譯註: 該問題已經脩複.), 因此我們強烈建議按照最壞的方式處理. 將所有包含變量 <code>y</code> 地址的 <code>uintptr</code> 類型變量當作 BUG 處理, 同時減少不必要的 <code>unsafe.Pointer</code> 到 <code>uintptr</code> 的轉換. 在第一個例子中, 有三個到 <code>uintptr</code> 的轉換, 字段偏移量的運算, 所有的轉換全在一個表達式完成.</p>
|
||||
<p>當調用一個庫函數, 併且返迴的是 <code>uintptr</code> 類型是, 比如下面反射包中的相關函數,
|
||||
返迴的結果應該立卽轉換爲 <code>unsafe.Pointer</code> 以確保指針指向的是相同的變量.</p>
|
||||
<pre><code class="lang-Go"><span class="hljs-keyword">package</span> reflect
|
||||
|
||||
<span class="hljs-keyword">func</span> (Value) Pointer() <span class="hljs-typename">uintptr</span>
|
||||
|
Reference in New Issue
Block a user