1. 程式人生 > >騰訊實習生面試2016兩道面試題目?(知乎)

騰訊實習生面試2016兩道面試題目?(知乎)

第一個問題:
引發bug的可能性有很多,形形色色的debug方法也有很多,它們各有各的優勢,並不存在通用的最優解,我目前用過的除錯方法有下面幾種:

1. 人肉除錯:
對於某些bug,直接根據程式的異常表現,就可以知道問題程式碼的具體位置,心裡逆推演一下相關程式碼,就可以找到問題產生的原因。
例:剛給客戶端加了個多執行緒模組,F5執行,等了30s。。。咦?客戶端介面怎麼還沒顯示出來?工作管理員一看,客戶端程序CPU佔用為0:八成是剛寫的程式碼死鎖了,直接Review程式碼吧。【Problem Solved】

2. 中斷除錯
依賴於IDE的除錯方法(寫C++一般用的都是VS吧),在可能出問題的程式碼位置打個斷點,或者等程式自己出異常中斷,或者手動加判斷中斷。程式中斷後,追溯函式呼叫堆疊,找到產生異常資料的程式碼。這是最方便的定位bug的方法,但前提是能夠在開發環境重現bug。
例:策劃突然跑過來說:“新做的技能怎麼沒傷害啊,是不是程式碼裡的傷害計算公式寫錯了?balabalabala”。。。計算技能傷害的程式碼位置打個斷點,一看資料,有個乘積因子載入以後的數值是0,“臥槽,你自己回去查下技能表是不是漏填了資料。” 【Problem Solved】

3. Log除錯

在經常出錯、或極有可能出錯的程式碼位置列印log,從而定位問題的原因。如果bug產生的程式碼沒有被log覆蓋到,可以通過臨時log排查可能導致出錯的問題模組。
例:測試:“剛釋出的測試客戶端怎麼XX介面打不開啊,程式看下唄!”程式猿:“log檔案發過來”。看完log:“介面裡有個資原始檔找不到,是不是美術沒上傳到SVN?”【Problem Solved】

4. Dump除錯
利用Windows API,在程式執行不正常時中斷,將此時的程式的記憶體映象輸出到一個dump檔案裡,然後利用WinDbg獲取中斷時的函式呼叫堆疊,從而定位出bug的程式碼,使用的前提是bug不會導致程式閃退,否則無法儲存dump檔案。
例:客服:“剛才有外網玩家反映切換地圖的時候程式報錯了。”程式猿:“有dump檔案嗎?”。分析完客服收集的dump檔案:“new記憶體的時候失敗了,加個記憶體池吧。”【Problem Solved】

5. 工具除錯

除了上面幾種通用的除錯方法以外,對於某些特定的問題,可以使用特定的工具進行除錯。
例:PIX可以用來除錯著色器;LeakDiag可以用來除錯記憶體洩漏;Vtune可以用來除錯效能。
Ps:由於這些工具通常會對程式效能產生比較明顯的影響,大型程式(比如遊戲引擎)通常會直接在程式碼層面整合相應的模組,並通過log將結果打印出來。

上面的五種方法基本是按照使用的困難程度升序排列的,對於具體的bug,在可以解決問題的情況下采用難度最低的debug方法才是最優解。而題目沒有給出bug的具體表現,所以這是一個開放性問題。

不過根據問題的四個關鍵詞:
關鍵詞一、 “多執行緒”
關鍵詞二、 “大量併發”
關鍵詞三、 “一百萬次出現一次”
關鍵詞四、“很難重現”
可以看出面試官為這個bug設定的屬性是:
  • 很難定位
  • 幾乎不可能在開發環境中重現
那麼基本可以pass掉人肉除錯、中斷除錯和工具除錯。所以此時只能通過收集外網環境中log或者dump檔案來分析。

Ps:產生bug的原因有很多,問題中並沒給出bug的具體表現,根本沒有辦法判斷bug產生的具體原因。題主和部分答主將答題思路往“臨界區”與“多執行緒同步”之類的方向靠,我覺得有點答非所問了,畢竟面試官的問題不是“造成bug的原因”,而是“如何debug”。

================================================================

第二個問題:
這個問題中,面試官明確提到了虛擬函式表被破壞,而虛擬函式表是放在程式的只讀資料段的,根本無法修改,所以只有可能是虛擬函式表指標被修改了。因此,我覺得物件崩潰的可能原因有下面幾個:

1. 訪問物件時,物件的建構函式尚未執行完畢。
例:物件是在某個函式內定義的靜態物件,比如某個單例函式:
static Type* GetInstance()
{
    static Type instance;
    return &instance;
}
多執行緒呼叫時,第一個執行緒會等待instance的建構函式執行完畢後再返回instance的地址,但由於static的存在,instance的建構函式只會執行一次,如果instance的建構函式尚未執行完畢,有第二個執行緒使用了GetInstance()函式,此時便會訪問一個尚未構造完成的物件。(Ps:據說新的C++標準修復了這個問題,但至少VS2013中這個問題是存在的。)

這個錯誤會造成程式中斷,此時直接根據函式呼叫堆疊定位到出錯的指標或物件即可。

2. 訪問物件前,某處程式碼手動執行了物件的解構函式。
在多型的情況下,手動呼叫物件的解構函式,雖然物件的記憶體並不會被釋放,但成員變數中的指標會變為野指標,此時訪問它們自然會造成程式崩潰。此外,執行解構函式時,C++還會做一些額外的工作:當執行完派生類的解構函式後,C++會將物件的虛擬函式表指標從派生類的虛擬函式表指向基類的虛擬函式表,因此,雖然派生類的記憶體空間還沒有被釋放,但此時已經無法再訪問派生類中定義的虛函數了。

這個錯誤會造成程式中斷,此時直接根據函式呼叫堆疊定位到出錯的指標或物件即可。

3. 記憶體越界訪問
虛擬函式表指標是由編譯器管理的,正常情況下開發者不會訪問或修改它的值。因此,若虛擬函式表指標被破壞了,說明程式中發生了記憶體越界訪問,造成了虛擬函式表指標被意外修改。

這個錯誤雖然會造成程式中斷,但出錯的位置往往不是錯誤發生的位置:
A. 如果物件不是通過new建立的
此時需要排查出錯物件宣告處的程式碼,觀察在它附近宣告的物件是否發生了記憶體越界訪問。(尤其要關注陣列物件,記憶體越界訪問通常是由於陣列下標越界導致的)。
B. 如果物件是通過new建立的
此時需要排查整個程式中所有的指標是否發生了越界訪問(仍然優先關注陣列)。
一種做法是過載new / delete操作符,分配記憶體時在返回的記憶體前後各自多分配一個int作為首尾標記,並將它們各自設定為一個特殊的值。每次釋放記憶體時,檢查首尾標記的值是否合法。如果不合法,說明該記憶體指標發生了越界訪問。