我已經閱讀了用C語言編程16位PIC單片機,並且在書中有這樣的肯定:
在a的開發和調試階段不過,在項目中,最好禁用所有優化,因為它們可能會修改所分析代碼的結構,並導致單步執行和斷點放置問題。
我承認我是一個有點困惑。我不知道作者是因為 C30評估期還是因為這是一種很好的做法。
我想知道您是否真的使用了這種做法,為什麼?
我已經閱讀了用C語言編程16位PIC單片機,並且在書中有這樣的肯定:
在a的開發和調試階段不過,在項目中,最好禁用所有優化,因為它們可能會修改所分析代碼的結構,並導致單步執行和斷點放置問題。
我承認我是一個有點困惑。我不知道作者是因為 C30評估期還是因為這是一種很好的做法。
我想知道您是否真的使用了這種做法,為什麼?
這在整個軟件工程中都是很標準的-當您優化代碼時,只要您不知道操作上的任何區別,編譯器就可以按照自己的意願重新安排很多事情。因此,例如,如果您在循環的每次迭代內初始化變量,而從不在循環內更改變量,則允許優化器將該初始化移出循環,以免浪費時間。
它還可能會意識到您計算了一個數字,然後您在覆蓋之前不做任何事情。在那種情況下,它可能會消除無用的計算。
優化的問題是,您需要在一些代碼上放置一個斷點,優化器已將其移動或刪除。在這種情況下,調試器將無法執行您想要的操作(通常,它將把斷點放置在附近)。因此,為使生成的代碼與您編寫的代碼更相似,請在調試過程中關閉優化-這可以確保您要中斷的代碼確實存在。
您需要對此小心謹慎,但是,根據您的代碼,優化可能會破壞事情!通常,被正確運行的優化器破壞的代碼實際上只是無法解決問題的錯誤代碼,因此您通常想弄清楚為什麼優化器會破壞它。
我已將這個問題發送給傑克·甘斯爾(Jack Ganssle),這就是他的回答:
丹尼爾,
我更喜歡使用調試中的任何優化進行調試發布的代碼。美國宇航局說:“測試你的飛行,測試你的飛行。”換句話說,不要先測試然後更改代碼!
但是,有時必須關閉優化程序才能使調試器正常工作。我試圖在我正在處理的模塊中將其關閉。出於這個原因,我相信保持文件較小,說幾百行代碼左右。
最好,傑克
取決於,通常所有工具(不僅是C30)都是如此。
優化通常以各種方式刪除和/或重組代碼。您的switch語句可能使用if / else構造重新實現,或者在某些情況下可能一起被刪除。 y = x * 16可能會替換為一系列左移,等等。儘管最後一種優化通常仍可以逐步執行,但其主要是對獲得ya的控制語句的重組。
由於您在C中定義的結構不再存在,它們被編譯器替換或重新排序為編譯器認為會更快或更快速地編譯的代碼,因此可能無法通過C代碼逐步調試程序使用更少的空間。這也可能導致無法從C列表中設置斷點,因為斷點的指令可能不再存在。例如,您可以嘗試在if語句中設置一個斷點,但是編譯器可能已刪除了該if。您可以嘗試在while或for循環中設置一個斷點,但是編譯器決定展開該循環,以使其不再存在。您應該始終在進行優化的情況下進行重新測試。這是您發現錯過重要的 volatile
及其導致間歇性故障(或其他怪異現象)的唯一方法。
對於嵌入式開發,無論如何,您必須謹慎進行優化。特別是在對時間要求嚴格的代碼段中,例如一些中斷。在這種情況下,您應該對彙編中的關鍵位進行編碼,或者使用編譯器指令來確保未對這些部分進行優化,以使您知道它們具有固定的執行時間或固定的最壞情況運行時間。
另一個難題可能是使代碼適合uC,您可能需要代碼密度優化才能將代碼簡單地適合芯片。這就是為什麼通常一個好主意是從一個家庭中最大的ROM容量uC開始,然後在代碼被鎖定後才選擇一個較小的ROM進行製造。
通常,我將使用計劃發布的任何設置進行調試。如果要發布優化的代碼,則可以使用優化的代碼進行調試。如果要發布未優化的代碼,則將使用未優化的代碼進行調試。我這樣做有兩個原因。首先,優化器可以產生足夠大的時序差異,從而導致最終產品的行為與未優化的代碼不同。其次,儘管大多數都不錯,但是編譯器供應商確實會犯錯誤,並且優化的代碼可能會與未優化的代碼產生不同的結果。因此,無論我打算發布什麼設置,我都希望獲得盡可能多的測試時間。如果我發現很難調試的特定代碼部分,則將暫時關閉優化器,進行調試以使代碼正常工作,然後重新打開優化器並再次進行測試。
我通常的策略是進行最終的優化(適當時,選擇最大的大小或速度),但是如果我需要調試其他跟踪信息,則暫時關閉優化。
一個典型的故障模式是,當您由於沒有在必要時將變量聲明為易失性而使優化增加導致以前看不見的錯誤浮出水面時,這是典型的故障模式。告訴編譯器哪些東西不應該“優化”是至關重要的。
在斷點的情況下肯定是有道理的...因為編譯器可以刪除很多實際上不影響內存的語句。
請考慮以下內容:
int i = 0;對於(int j = 0; j < 10; j ++){i + = j;}返回0;
可以完全優化(因為 i
從未被讀取)。從斷點的角度來看,它基本上跳過了所有代碼,甚至根本不在那兒……。我想這就是為什麼在睡眠類型函數中您經常會看到類似這樣的原因:
for(int j = delay; j!= 0; j-){asm(“ nop”); asm(“ nop”);}返回0;
使用將要發布的任何形式,調試器和用於調試的編譯會隱藏很多(很多)直到編譯發布才可見的錯誤。到那時,要找到這些錯誤要比進行調試要困難得多。到現在已有20多年了,我再也沒有使用過gdb或其他類似調試器的東西,不需要監視變量或單步執行。每天數百到數千行。
編譯為調試,然後再編譯為發布可能會花費兩倍甚至兩倍以上的精力。如果您陷入困境並且必須使用調試器之類的工具,然後進行編譯以使調試器解決特定問題,然後恢復為正常運行。
其他問題也同樣存在,例如優化器使代碼速度更快,因此,特別是對於嵌入式而言,您的時序更改會帶有編譯器選項,並且可能會影響程序的功能,因此在此處再次使用整個階段的可交付編譯選項。編譯器也是程序,有錯誤,優化器會出錯,有些人對此不抱任何信念。如果是這種情況,那麼不進行優化就可以編譯,沒錯,只要一直這樣做就可以了。我更喜歡的路徑是進行編譯以進行優化,然後,如果我懷疑編譯器問題禁用了優化(如果解決了該問題),通常會來回往復,有時會檢查彙編器輸出以找出原因。
我總是使用-O0(gcc選項關閉優化功能)開發代碼。當我感覺到我想開始讓事情更趨於發佈時,我將從-Os(針對大小進行優化)開始,因為通常可以在緩存中保留的代碼越多越好,我發現gdb與-O0代碼一起使用時效果更好,如果您必須進入程序集,則遵循起來會容易得多。
在-O0和-Os之間切換還可讓您查看編譯器對代碼的作用。有時候這是很有趣的教育,它還可以發現編譯器錯誤...那些使您費力地試圖弄清楚代碼出什麼問題的討厭的東西!
如果我真的需要,我將開始在帶有-gc-sections的-fdata-sections和-fcode-sections中添加,這使鏈接器可以刪除實際上不使用的全部功能和數據段。您可以修補很多小東西,以嘗試進一步縮小或加快速度,但總的來說,這是我最終使用的唯一技巧,而任何較小或更快的東西我都會處理-組裝。
是的,暫時禁用調試期間的優化一直是最佳實踐,原因有三點:
許多人朝著這個方向走得更遠,並且打開斷言的船。
簡單:優化是費時的,如果以後在開發中必須更改那段代碼,可能沒有用。因此,它們很可能浪費時間和金錢。
它們對於完成的模塊很有用;代碼中最有可能不需要更改的部分。
如果您使用的是調試器,那麼我會禁用優化功能並啟用調試。
我個人發現PIC調試器會導致更多問題,而無法解決。
我只是使用printf()到USART調試用C18編寫的程序。
大多數反對在編譯中啟用優化的論點歸結為:
恕我直言,前兩個是合法的,第三個不是很多。這通常意味著您有一些錯誤的代碼,或者依賴於對語言/實現的不安全利用,或者作者只是對Undefined Undefined Behaviour的忠實擁護者。一兩個關於未定義行為的說法,這篇文章是關於編譯器如何利用它的: http://blog.regehr.org/archives/761