日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
軟件開(kāi)發(fā)探索之道:讓自己成為知識(shí)的所有者

在做軟件開(kāi)發(fā)的時(shí)候,總會(huì)有一些奇奇怪怪的問(wèn)題難以解答:

創(chuàng)新互聯(lián)是一家專業(yè)提供呼和浩特企業(yè)網(wǎng)站建設(shè),專注與網(wǎng)站建設(shè)、網(wǎng)站制作、H5頁(yè)面制作、小程序制作等業(yè)務(wù)。10年已為呼和浩特眾多企業(yè)、政府機(jī)構(gòu)等服務(wù)。創(chuàng)新互聯(lián)專業(yè)網(wǎng)站設(shè)計(jì)公司優(yōu)惠進(jìn)行中。

  • 棧是向上增長(zhǎng)還是向下增長(zhǎng)?(這其實(shí)是個(gè)不嚴(yán)謹(jǐn)?shù)膯?wèn)題)
  • arm 是 little endian 還是 big endian?
  • 閉包究竟是一個(gè)什么樣的數(shù)據(jù)結(jié)構(gòu)?它占用多少內(nèi)存?
  • ...

這些讓人摸不著頭腦的問(wèn)題,只要你耐心查找,在 stackoverflow 或者各種論壇上,一般能夠找到答案。不過(guò),別人給出來(lái)的答案很可能是模棱兩可的,不好理解的,甚至是錯(cuò)誤的。我們需要花時(shí)間甄別那些正確的、并且精準(zhǔn)的答案,還需要花時(shí)間閱讀這些答案。有時(shí)候,即便是你得到了答案甚至記住了答案,你可能還是沒(méi)有完全理解別人給出的答案。當(dāng)你需要把這樣的答案講給別人時(shí),你會(huì)發(fā)現(xiàn)自己似乎無(wú)法講得清楚。

在我的職業(yè)生涯中,遇見(jiàn)過(guò)很多所謂的「高手」,漫長(zhǎng)的職業(yè)生涯讓他們遇見(jiàn)了各種奇葩的問(wèn)題,通過(guò)各種知識(shí)搜索和整理的手段,他們也記住了這些問(wèn)題的答案。他們經(jīng)常能拋出一些冷門的知識(shí),知識(shí)儲(chǔ)備之豐富讓我嘆為觀止。但當(dāng)我想深入下去時(shí),就發(fā)現(xiàn)他們對(duì)事物的理解不過(guò)是一個(gè)指向別處的引用(reference),是借來(lái)(borrow)的知識(shí),自己沒(méi)有知識(shí)的所有權(quán)(ownership),所以往往容易語(yǔ)焉不詳,只能給出淺層的回答。

那么,如何避免這種情況,讓自己成為知識(shí)的所有者呢?

我們要學(xué)會(huì)不依賴別人的斷言,單單通過(guò)代碼本身來(lái)探索問(wèn)題的答案。作為開(kāi)發(fā)者,我們最大的優(yōu)勢(shì)就是我們研究的對(duì)象,計(jì)算機(jī)和計(jì)算機(jī)軟件,就放在離我們唾手可得的地方。我們只要想辦法用代碼構(gòu)造研究這個(gè)問(wèn)題的實(shí)驗(yàn),就能不斷迭代夠逐漸找到答案。而且,這答案是第一手的,不是別人咀嚼后喂給你的,而是你通過(guò)實(shí)驗(yàn)驗(yàn)證出來(lái)的,所以它是你自己的知識(shí),即便過(guò)了十年二十年,你依然能清晰地給出答案,或者至少給出通往這個(gè)答案的途徑。

問(wèn)有意思的問(wèn)題

最近在我的極客時(shí)間的專欄《陳天 · Rust 第一課》中,有個(gè)同學(xué)在看到我畫(huà)的這張圖時(shí):

問(wèn)了這樣一個(gè)問(wèn)題:

虛表是每個(gè)類有一份,還是每個(gè)對(duì)象有一份,還是每個(gè)胖指針有一份?

這是一個(gè)非常棒的問(wèn)題。我不知道有多少人在學(xué)習(xí)的時(shí)候會(huì)發(fā)出這樣的疑問(wèn),但我猜很少,因?yàn)橹辽傥抑霸谥辈ブv Rust 時(shí),在我公司內(nèi)部講 Rust 時(shí),沒(méi)有人關(guān)心過(guò)這個(gè)問(wèn)題。

而 問(wèn)對(duì)問(wèn)題,比知道答案更重要 。一個(gè)好的問(wèn)題,就已經(jīng)離知識(shí)很近了。

<愛(ài)因斯坦>

如何才能問(wèn)出有意思的問(wèn)題?

我在學(xué)習(xí) trait object 的時(shí)候,也問(wèn)過(guò)同樣的問(wèn)題,并且順著問(wèn)題,找到了答案。你想想,什么樣的思考會(huì)觸發(fā)問(wèn)這個(gè)問(wèn)題呢?

也許來(lái)自對(duì)比學(xué)習(xí)(我自己的情況):因?yàn)?C++ 每個(gè)類有一個(gè)自己的虛表,所以不免會(huì)好奇 trait object 是不是也是類似的實(shí)現(xiàn)?

也許來(lái)自對(duì)內(nèi)存效率的擔(dān)憂:trait object 有個(gè)指針指向虛表,那么如果在每個(gè) trait object 生成時(shí)都生成一張?zhí)摫?,那么很浪費(fèi)內(nèi)存啊。對(duì)于上面的 Write trait,還好,只有幾個(gè)方法,但對(duì)一些比較大的 trait,如 Iterator,有近七十個(gè)方法,也就是說(shuō)光這些方法組成的虛表,就有五百多字節(jié)!如果每個(gè) trait object 都自己生成這樣一張表,內(nèi)存占用多可怕!所以如果不搞明白,不敢大量使用啊。

也許還有其它什么思考觸發(fā)了這個(gè)問(wèn)題。

不管怎么樣,能問(wèn)出好的問(wèn)題,一定會(huì)現(xiàn)有一些先驗(yàn)知識(shí),然后通過(guò)細(xì)致的觀察,深入的思考,才會(huì)慢慢萌發(fā)問(wèn)題。

從假設(shè)到通過(guò)實(shí)驗(yàn)驗(yàn)證假設(shè)

那么,有了好問(wèn)題,我們?nèi)绾谓獯疬@個(gè)問(wèn)題呢?

我們可以根據(jù)自己已有的知識(shí),思考最可能接近真相的方向,然后動(dòng)手做實(shí)驗(yàn)來(lái)驗(yàn)證自己的假設(shè)。對(duì)于這個(gè)問(wèn)題,我認(rèn)為為每個(gè) trait object 生成一張表效率太低,不太可能,所以傾向于像 C++ 那樣,每個(gè)類型都有靜態(tài)的虛表。既然我有了這樣的假設(shè),那么怎么驗(yàn)證它呢?我可以用兩個(gè)字符串分別生成 trait object,然后打印虛表的地址進(jìn)行對(duì)比。如果一致,那么符合我的假設(shè):每個(gè)類型都有靜態(tài)的虛表。

實(shí)驗(yàn)一

有了這個(gè)方向,查閱資料,寫(xiě)出下面的第一個(gè)實(shí)驗(yàn)的代碼并非難事:

 
 
 
 
  1. use std::fmt::Debug; 
  2. use std::mem::transmute; 
  3.  
  4. fn main() { 
  5.     let s1 = String::from("hello"); 
  6.     let s2 = String::from("goodbye"); 
  7.     let w1: &dyn Debug = &s1; 
  8.     let w2: &dyn Debug = &s2; 
  9.  
  10.     // 強(qiáng)行把 triat object 轉(zhuǎn)換成兩個(gè)地址 (usize, usize) 
  11.     // 這是不安全的,所以是 unsafe 
  12.     let (addr1, vtable1) = unsafe { transmute::<_, (usize, usize)>(w1 as *const dyn Debug) }; 
  13.     let (addr2, vtable2) = unsafe { transmute::<_, (usize, usize)>(w2 as *const dyn Debug) }; 
  14.      
  15.     // trait object(s / Display) 的 ptr 地址和 vtable 地址 
  16.     println!("addr1: 0x{:x}, vtable1: 0x{:x}", addr1, vtable1); 
  17.     // trait object(s / Debug) 的 ptr 地址和 vtable 地址 
  18.     println!("addr2: 0x{:x}, vtable2: 0x{:x}", addr2, vtable2); 
  19.  
  20.     // String 類型擁有相同的 vtable? 
  21.     assert_eq!(vtable1, vtable2); 

如果你在 rust playground 里運(yùn)行,會(huì)得到下面的結(jié)果:

addr1: 0x7ffd1c524910, vtable1: 0x556591eae4c8
addr2: 0x7ffd1c524928, vtable2: 0x556591eae4c8

從實(shí)驗(yàn)一中,我們得出結(jié)論: 虛表是共享的,不是每一個(gè) trait object 都有一張?zhí)摫?/b> 。從虛表的地址上看,它既不是堆地址,也不是棧地址。目測(cè)像是代碼段或者數(shù)據(jù)段的地址?

你看,我們通過(guò)觀測(cè)實(shí)驗(yàn)結(jié)果,又有了新的發(fā)現(xiàn),同時(shí)有了新的問(wèn)題。

于是我們繼續(xù)迭代。

實(shí)驗(yàn)二

在實(shí)驗(yàn)一的基礎(chǔ)上,我們可以定義一個(gè)靜態(tài)變量 V,打印一下它的地址(DATA 段),以及打印一下 main() 函數(shù)的地址(TEXT 段)來(lái)比較:

 
 
 
 
  1. static V: i32 = 0; 
  2. println!("V: {:p}, main(): {:p}", &V, main as *const ()); 

打印結(jié)果(注意每次編譯后運(yùn)行地址都會(huì)不同):

 
 
 
 
  1. addr1: 0x7fff2dd3e7f8, vtable1: 0x557a21b9e488 
  2. addr2: 0x7fff2dd3e810, vtable2: 0x557a21b9e488 
  3. V: 0x557a21b910ec, main(): 0x557a21b63e40 

Bingo!實(shí)驗(yàn)二證明了我們的猜測(cè)沒(méi)錯(cuò), 虛表是編譯時(shí)就生成好,塞入二進(jìn)制文件中的 。當(dāng)生成 trait object 時(shí),根據(jù)是哪個(gè)類型,再指向?qū)?yīng)的位置。

那么,Rust 為每個(gè)類型(比如 String )編譯時(shí)只生成一個(gè) vtable,對(duì)么?

我們目前很接近真相,但還有未解的疑問(wèn)。從目前的實(shí)驗(yàn)中,我們還無(wú)法得出這個(gè)結(jié)論。實(shí)驗(yàn)一里,我們只用了 Debug trait,這個(gè)樣本太小,不具備普遍性。如果對(duì)同一個(gè)數(shù)據(jù)類型(比如 String)使用不同的 trait,會(huì)導(dǎo)致不同的結(jié)果么?我們并不知道。如果結(jié)果相同,那么我們就大概率可以確定,一個(gè)類型一張?zhí)摫?,否則,就應(yīng)該是每個(gè)類型的每個(gè) trait 實(shí)現(xiàn),都有一張?zhí)摫怼?/p>

實(shí)驗(yàn)三

于是在實(shí)驗(yàn)三里,我們用同一個(gè)類型的兩個(gè)不同的 Trait,來(lái)生成不同的 trait object,看看其虛表是否是同一個(gè)地址:

 
 
 
 
  1. use std::fmt::{Debug, Display}; 
  2. use std::mem::transmute; 
  3.  
  4.  
  5. fn main() { 
  6.     let s1 = String::from("hello world!"); 
  7.     let s2 = String::from("goodbye world!"); 
  8.     // Display / Debug trait object for s 
  9.     let w1: &dyn Display = &s1; 
  10.     let w2: &dyn Debug = &s1; 
  11.  
  12.  
  13.     // Display / Debug trait object for s1 
  14.     let w3: &dyn Display = &s2; 
  15.     let w4: &dyn Debug = &s2; 
  16.  
  17.  
  18.     // 強(qiáng)行把 triat object 轉(zhuǎn)換成兩個(gè)地址 (usize, usize) 
  19.     // 這是不安全的,所以是 unsafe 
  20.     let (addr1, vtable1) = unsafe { transmute::<_, (usize, usize)>(w1 as *const dyn Display) }; 
  21.     let (addr2, vtable2) = unsafe { transmute::<_, (usize, usize)>(w2 as *const dyn Debug) }; 
  22.     let (addr3, vtable3) = unsafe { transmute::<_, (usize, usize)>(w3 as *const dyn Display) }; 
  23.     let (addr4, vtable4) = unsafe { transmute::<_, (usize, usize)>(w4 as *const dyn Debug) }; 
  24.  
  25.  
  26.     // s 和 s1 在棧上的地址,以及 main 在 TEXT 段的地址 
  27.     println!( 
  28.         "s1: {:p}, s2: {:p}, main(): {:p}", 
  29.         &s1, &s2, main as *const () 
  30.     ); 
  31.     // trait object(s / Display) 的 ptr 地址和 vtable 地址 
  32.     println!("addr1: 0x{:x}, vtable1: 0x{:x}", addr1, vtable1); 
  33.     // trait object(s / Debug) 的 ptr 地址和 vtable 地址 
  34.     println!("addr2: 0x{:x}, vtable2: 0x{:x}", addr2, vtable2); 
  35.  
  36.  
  37.     // trait object(s1 / Display) 的 ptr 地址和 vtable 地址 
  38.     println!("addr3: 0x{:x}, vtable3: 0x{:x}", addr3, vtable3); 
  39.  
  40.  
  41.     // trait object(s1 / Display) 的 ptr 地址和 vtable 地址 
  42.     println!("addr4: 0x{:x}, vtable4: 0x{:x}", addr4, vtable4); 
  43.  
  44.  
  45.     // 指向同一個(gè)數(shù)據(jù)的 trait object 其 ptr 地址相同 
  46.     assert_eq!(addr1, addr2); 
  47.     assert_eq!(addr3, addr4); 
  48.  
  49.  
  50.     // 指向同一種類型的同一個(gè) trait 的 vtable 地址相同 
  51.     // 這里都是 String + Display 
  52.     assert_eq!(vtable1, vtable3); 
  53.     // 這里都是 String + Debug 
  54.     assert_eq!(vtable2, vtable4); 

結(jié)果令人驚喜:String + Display 生成的 trait object,和 String + Debug 生成的 trait object,使用的是不同的 vtable:

 
 
 
 
  1. s1: 0x7ffc7d427a08, s2: 0x7ffc7d427a20, main(): 0x561b76ff2e90 
  2. addr1: 0x7ffc7d427a08, vtable1: 0x561b7702d3b8 
  3. addr2: 0x7ffc7d427a08, vtable2: 0x561b7702d3d8 
  4. addr3: 0x7ffc7d427a20, vtable3: 0x561b7702d3b8 
  5. addr4: 0x7ffc7d427a20, vtable4: 0x561b7702d3d8 

所以,我們可以確定, 虛表是每個(gè) (Trait, Type) 一份,在編譯時(shí)就生成好了 。

那么,編譯器在什么時(shí)機(jī)來(lái)生成這張?zhí)摫砟兀坑欣碛赏茢?,在編譯器編譯 impl 某個(gè) trait 的代碼時(shí)生成了虛表,比如:

 
 
 
 
  1. impl Debug for String {...} 

因?yàn)榇藭r(shí)編譯器有生成虛表所需要的一切信息:

  • 數(shù)據(jù)如何銷毀:String 的 drop 方法的地址此時(shí)需要已經(jīng)編譯得出
  • 數(shù)據(jù)的大小和對(duì)齊:此刻是 String 類型,所以大小 24 字節(jié),對(duì)齊 8 字節(jié)
  • trait 方法:在編譯 impl Debug 時(shí)就已經(jīng)得到 fmt() 方法的地址

如果我是編譯器的開(kāi)發(fā)者,此時(shí)不做,更待何時(shí)?所以我們可以做出這個(gè)推斷。這個(gè)推斷邏輯自洽,看上去非常合理,大概率是對(duì)的。不過(guò)要驗(yàn)證起來(lái)不那么容易,除非我們繼續(xù)在 Rust 編譯器源碼中做實(shí)驗(yàn)。

從實(shí)驗(yàn)結(jié)果中最終得出結(jié)論

好,綜合上述三個(gè)實(shí)驗(yàn),我們的腦海中,已經(jīng)可以構(gòu)筑出這樣一幅圖:

此刻,我們就完美地找到了一開(kāi)始的問(wèn)題我們想要的答案。對(duì)于開(kāi)頭的問(wèn)題,我是這么回答的:

好問(wèn)題。這個(gè)在講 trait 的那一課有講到。虛表在每個(gè) impl TraitA for TypeB {} 實(shí)現(xiàn)時(shí)就會(huì)編譯出一份。比如 String 的 Debug 實(shí)現(xiàn), String 的 Display 實(shí)現(xiàn)各有一份虛表,它們?cè)诰幾g時(shí)就生成并放在了二進(jìn)制文件中(大概是 RODATA 段中)。 所以虛表是每個(gè) (Trait, Type) 一份。并且在編譯時(shí)就生成好了。 如果你感興趣,可以在 playground 里運(yùn)行這段代碼(這是后面講 trait 時(shí)使用的代碼): https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=89311eb50772982723a39b23874b20d6 。限于篇幅,代碼就不貼了。

因?yàn)槲易约和ㄟ^(guò)做實(shí)驗(yàn),找到了答案,所以,我對(duì)自己的結(jié)論和推斷都很有信心。同時(shí),因?yàn)檫@是我自己探索出來(lái)的知識(shí),我并非借用別人腦海中的想法,而是對(duì)它擁有所有權(quán),所以,我可以自如地從各個(gè)角度來(lái)構(gòu)筑我的答案。

小結(jié)

在韋氏詞典中,是這么定義科學(xué)方法的:科學(xué)方法是一種有系統(tǒng)地尋求知識(shí)的程序,涉及了以下三個(gè)步驟:?jiǎn)栴}的認(rèn)知與表述、實(shí)驗(yàn)數(shù)據(jù)的收集、假說(shuō)的構(gòu)成與測(cè)試。我們?cè)谔剿?Rust 的 vtable 是如何構(gòu)建的過(guò)程中,使用了科學(xué)方法。它是一個(gè)不斷迭代的過(guò)程,從觀測(cè)開(kāi)始,一路經(jīng)歷問(wèn)問(wèn)題,做出假設(shè),構(gòu)建實(shí)驗(yàn)來(lái)驗(yàn)證假設(shè),觀察實(shí)驗(yàn)結(jié)果,提出新的問(wèn)題,進(jìn)一步迭代下去,直到我們形成了一個(gè)自洽的理論:

本文我們通過(guò)一個(gè) Rust 的例子來(lái)探討這個(gè)方法。當(dāng)這個(gè)方法本身跟 Rust 無(wú)關(guān)。我們?cè)趯W(xué)習(xí)編程語(yǔ)言,使用第三方庫(kù),構(gòu)建復(fù)雜的系統(tǒng),都可以用這個(gè)方法。如果你能夠掌握和使用這個(gè)方法,那么,慢慢地你就能成為知識(shí)的所有者。


分享標(biāo)題:軟件開(kāi)發(fā)探索之道:讓自己成為知識(shí)的所有者
網(wǎng)站地址:http://www.5511xx.com/article/djedooo.html