軟體開發人員們在開發產品級代碼時常會面對一個艱難的選擇,你總是希望你的代碼效能優越,這意味著你 需要在高最佳化層級上編譯它;同時,你可能希望調試你加入產品中的這份二進位代碼,而不是編譯時間沒有經過 最佳化的源檔案。如果你嘗試過調試最佳化過的代碼,你可能已經知道這其中的難處了:
原始碼語句不按順序執行,或者在你希望它們執行的時候它們沒有;
變數沒有按預期地進行更新;
變數沒有定義的值,甚至沒有一個定義的標識;
在調試器內對變數的更新對程式執行不起作用 。
這不是因為編譯器出了什麼差錯,它設計的初衷就是為了保留你程式的結果和外部行為,而不是它 在調試器中的瞬態和內部行為。
新一代編譯器
在2012年年中IBM發布了IBMPower系統上C/C++和 Fortran編譯器的最新版本:XLC/C++編譯器V12.1和XLFortran編譯器V14.1,均對應AIX和PowerLinux平台。這 些新的編譯器提供了一系列數值的選擇以供調試最佳化過的代碼,你可以自由選擇在完全最佳化代碼和完全可調試 代碼之間的權衡層級。在大多數其它編譯器中,開發人員有兩個可用的選擇:
編譯一個沒有最佳化的調試版本 (例如使用-g),或
一個最佳化過的版本,但是可調試性較差(例如使用-O2)。
在新的XLC++ V12.1和XL FortranV14.1編譯器中,你可以使用一系列-g的值,從-g0一直到-g9,調試層級越低,在調試期間 觀測的錯誤可能性越大,這意味著你可以自由權衡可調試性和效能。在程式最佳化時編譯器會保障各層級的可調 試性,並在調試過程中在保證預期行為的情況下有效地將應用的效能最大化。
執行順序
編譯器 在最佳化過程中經常會在不改變程式結果的前提下重新編排邏輯,編譯器這麼做可能是為了直接提高程式的效能 ,或者是為了讓接下來的最佳化能提高程式的效能。此外,為了提高處理器輸送量或是其它的原因,當主最佳化階 段完成、指令產生時,原始碼行相關的指令序列可能也會被重新排序,
這些編譯器的最佳化有兩個主要 的影響:第一,如果你按原始碼的行數單步跟蹤程式,那麼程式可能不會按照正確的順序執行。第二,如果你 在一個過程的開始設了個斷點,沒有人能保證這過程的參數在這時會含有正確的值,程式甚至根本不會進入這 個過程。這是因為編譯器在最佳化過程中將被調用的過程代碼內聯至調用處。在XLC/C++和 XLFortran的早先版 本中,舊的-g行為會產生全部調試資訊,但它對於一個最佳化過的程式是否有用就不得而知了,因此,在用-g和 -O編譯的程式中,當你試圖去調試並觀測某個特定過程被調用時的參數是什麼時,你甚至不能確定過程入口處 設立的斷點是否可以讓你看到這些參數的實際值。然而,-g3或更高的調試層級可以保證在過程的入口處參數 是有效且可見的。
最佳化代碼中的某些語句可能並沒有被執行到,而另一些可能比在它們之前的語句更先被 執行。如果你在一個給定的原始碼行中設立一個斷點,你不能確定邏輯上在這之前的行中變數被賦的值是否正 確。例如,考慮一下程式段:
10 x=x+1;
11fl=fl1/fl2;
12 y=y+1;
如果我們在第11行設立一個斷點並運行程式,調試器會在與第11行關聯的第一條指令處 停下,但此時第10行可能還沒有被執行過,因為第10行與第11行是無關的,編譯器可以自由將它們調換順序。 如果你想要逐步執行這段代碼,你可能會發現我們在停在第10行前就已經執行到了11行,這是一個很強的訊號 (但不是一定的),說明與第10行相關的指令都還沒有被執行到。 在這個例子中編譯器會將除法儘早做好, 因為除法指令有很長的延遲,而此時一個先進的流水線式處理器可以在除法進行時繼續工作,這樣的話沒有關 聯上的行為,例如將x的值讀入寄存器並將它加1,可能會同時進行。假設我們對在第11行處的除法結果有興趣 。在沒有最佳化過的程式中,我們只需要單步到第12行,再查看fl的值。但在最佳化過的程式中,單步到第12步不 能保證所有與第11行相關的操作都已經完成,我們可能已經完成了除法但還沒有將結果存回記憶體,因此,確定 變數已經可以顯示正確結果的唯一方法是逐步執行機器指令直到你看到除法運算的結果被寫回了記憶體空間。如 果你只關心結果的值(而不是驗證是否更新了變數),取而代之你可以在除法運算更新了其目標寄存器的同時 列印它的值。
當你用 XLC/C++ V12.1或 XL FortranV14.1的-g8或更高的選項編譯器時,調試器可以 得到每個可執行語句開始時程式的狀態。在先前的例子上使用-g8可以確保當你到達第11行的斷點時,第10行 的加法已經完成,而且你可以通過調試器來得到其結果。