題:
為什麼printf()對調試嵌入式系統不利?
tarabyte
2014-04-04 01:54:46 UTC
view on stackexchange narkive permalink

我想嘗試使用 printf()調試基於微控制器的項目是一件壞事。

我知道您沒有預定義的輸出位置,並且它可能會消耗寶貴的密碼。同時,我已經看到人們使用一個自定義 DEBUG_PRINT()宏來消耗UART TX引腳輸出到IDE終端。

誰告訴你這很糟糕? “通常不是最好的”通常與不合格的“壞”相同。
所有這些都討論了有多少開銷,如果您需要做的只是輸出“我在這裡”消息,那麼您根本不需要printf,而只是一個將字符串發送到UART的例程。這樣,加上初始化UART的代碼可能不到100個字節的代碼。增加輸出幾個十六進制值的能力並不會增加太多。
由於包含而佔用內存,這就是它不好的原因。
@ChetanBhargava-C頭文件通常不向可執行文件添加代碼。它們包含聲明;如果其餘代碼未使用聲明的內容,則這些內容的代碼也不會鏈接。如果您使用`printf`,那麼,實現`printf`所需的所有代碼都會被鏈接進入可執行文件。但這是因為代碼使用了它,而不是因為標頭。
-1
@tcrosley問題特別是關於printf()而不是cout或其他問題。
@tcrosley我認為,如果您有一個好的現代編譯器,並且在沒有格式字符串gcc的簡單情況下使用printf,而大多數其他人則用更有效的簡單調用代替了此建議,那麼此建議可能無濟於事。
@Vality-我不知道。我將不得不檢查我們的995美元PIC32優化編譯器的反彙編輸出,並查看是否這樣做。 (基於gcc,Microchip對其進行了進一步優化。)
@tcrosley,價值:或者您可以只使用`puts()`。然後,您甚至不需要提供格式字符串。
@Vality:這是什麼“在沒有格式字符串的簡單情況下使用printf()”?第一個參數始終是格式字符串。直接在該參數中傳遞變量是一個不好的主意。工具鏈可以優化的唯一情況是格式字符串是原義的“%s”。
@BenVoigt為什麼將變量直接傳遞到那裡是個壞主意?只要不包含任何替換字符,任何char *都可以嗎?通常,您也只想在其中輸入一個簡單的文字。 printf(“ foo”);很好,char foo [] ='bar'也是如此; printf(foo);
@BenVoigt本質上,我建議人們如果不想使用stdio庫的任何部分,可以很容易地編寫自己的puts()。
@Vality:因為它無法維護。您編寫`char foo [] =“ bar”;`,然後您的同事將其更改為`char foo [] =“%bar”;`,現在您遇到了問題。另外,您還要求`printf`做額外的工作來查找格式代碼。只需使用puts()即可避免這些問題。
@BenVoigt也許您對第一個評論很了解。我不是在提倡使用它,只是試圖用一個好的編譯器來解釋它並不需要太慢,我之前的評論是關於在簡單情況下gcc如何簡化它在編譯時確定不包含%的字符串上的printf的所有內容。
八 答案:
Spehro Pefhany
2014-04-04 02:38:50 UTC
view on stackexchange narkive permalink

使用printf()可能會帶來一些缺點。請記住,“嵌入式系統”的範圍可以從具有數百字節程序存儲器的東西到具有千兆字節RAM和TB級非易失性存儲器的成熟的機架安裝QNX RTOS驅動的系統。

  • 它需要某個地方來發送數據。也許您已經在系統上具有adebug或編程端口,也許您沒有。如果您不這樣做(或您所擁有的那個人沒有工作),那麼它就不是很方便。

  • 這並不是在所有情況下的輕量級功能。如果您的微控制器只有幾個K的內存,這可能是一件大事,因為在printf中進行鏈接可能會自己吞噬4K。如果您擁有32K或256K微控制器,那可能不是問題,更不用說擁有大型嵌入式系統了。

  • 查找與內存分配或中斷有關的某些類型的問題很少或沒有用,並且可以在不包含語句的情況下更改程序的行為。

  • 它對於處理對時間敏感的東西是毫無用處的。最好使用邏輯分析儀和示波器,協議分析儀甚至是模擬器。

  • 如果您的程序很大,則必須重新編譯很多次更改printf語句並對其進行更改時,可能會浪費大量時間。

這有什麼好處-這是一種以預格式化的方式輸出數據的快速方法,每個C程序員都知道如何使用-零學習曲線。如果您需要為正在調試的Kalman濾波器吐出一個矩陣,最好以MATLAB可以讀取的格式吐出它。這肯定比在調試器或仿真器中一次查看RAM位置要好。 。

我認為這不是箭袋中的無用箭頭,但應與gdb或其他調試器,仿真器,邏輯分析儀,示波器,靜態代碼分析工具,代碼覆蓋率工具和以此類推。

大多數`printf()`實現不是線程安全的(即不可重入),它不是交易殺手,而是在多線程環境中使用時要牢記的一點。
@JRobert帶來了一個好處。.即使在沒有操作系統的環境中,也很難對ISR進行很多有用的直接調試。當然,如果您在ISR中執行printf()或浮點數學運算,則該方法可能已關閉。
@JRobert軟件開發人員在多線程環境(在無法使用邏輯分析儀和示波器的硬件設置中)工作的調試工具是什麼?
過去,我已經開發了自己的線程安全的printf();。使用赤腳puts()或putchar()等效項將非常簡潔的數據吐出到終端;將二進制數據存儲在測試運行後轉儲並解釋的數組中;使用I / O端口使LED閃爍或生成脈衝以使用示波器進行定時測量;向D / A吐出一個數字,並用VOM進行測量...該列表與您的想像力一樣長,而與預算一樣大!:)
Scott Seidman
2014-04-04 03:03:47 UTC
view on stackexchange narkive permalink

除了一些其他好的答案外,以串行波特率將數據發送到端口的行為相對於循環時間而言可能會非常緩慢,並且會影響其餘程序的功能(例如可以進行任何調試過程)。

正如其他人告訴您的那樣,使用這種技術沒有什麼“不好”的地方,但是與許多其他調試技術一樣,它也有其局限性。只要您知道並能夠解決這些局限性,它便可以非常方便地幫助您正確編寫代碼。

嵌入式系統具有一定的不透明性,通常使調試過程有些困難。一個問題。

+1為“嵌入式系統具有一定的不透明度”。儘管我擔心只有在嵌入式方面有豐富經驗的人才能理解此聲明,但它確實可以很好地概括此情況。實際上,它接近“嵌入式”的定義。
embedded.kyle
2014-04-04 02:29:44 UTC
view on stackexchange narkive permalink

嘗試在微控制器上使用 printf 時會遇到兩個主要問題。

首先,將輸出管道傳輸到正確的端口可能很麻煩。不總是。但是某些平台比其他平台困難。某些配置文件的文檔可能不完善,可能需要進行大量實驗。

第二個是內存。完整的 printf 庫可以是BIG。有時您不需要所有格式說明符,但可以使用專門的版本。例如,由AVR提供的 stdio.h 包含三種不同的大小和功能不同的 printf

由於所有提到的功能的完整實現都變得很大,因此可以使用鏈接器選項選擇 vfprintf()的三種不同樣式。默認的 vfprintf()實現除浮點轉換之外的所有上述功能。提供了 vfprintf()的最小版本,該版本僅實現了非常基本的整數和字符串轉換功能,但是只能使用轉換標誌(這些標誌指定附加選項)可以從格式規範中正確解析,但隨後將其忽略)。

我有一個實例,其中沒有庫可用,並且內存最少。因此,我別無選擇,只能使用自定義宏。但是是否使用 printf 確實是滿足您要求的一種。

請拒絕者解釋一下我的答案中有什麼不正確,以便在以後的項目中避免我的錯誤?
njahnke
2014-04-04 19:19:53 UTC
view on stackexchange narkive permalink

要補充Spehro Pefhany所說的“對時間敏感的東西”:讓我們舉個例子。假設您有一台陀螺儀,您的嵌入式系統每秒從該陀螺儀進行1000次測量。您想調試這些測量,所以需要將它們打印出來。問題:將它們打印出來會導致系統太忙,無法每秒讀取1,000個測量值,這會導致陀螺儀的緩衝區溢出,從而導致讀取(並打印)損壞的數據。因此,通過打印數據,您已損壞了數據,使您認為實際上可能沒有讀取數據時存在錯誤。所謂的heisenbug。

大聲笑!“ heisenbug”真的是一個技術術語嗎?我想這與粒子狀態的測量和海森堡原理有關...
Theran
2014-04-04 02:41:04 UTC
view on stackexchange narkive permalink

不使用printf()進行調試的更大原因是它通常效率低下,不足且不必要。

效率低下:相對於小型微控制器上可用的功能,printf()和kin使用大量閃存和RAM,但實際調試中效率較低。更改記錄的內容需要重新編譯和重新編程目標,這會減慢該過程。它還會用完一個UART,否則您可能會用它來做有用的工作。

不足:通過串行鏈接可以輸出的細節太多。如果程序掛起,則您不知道確切的位置,只有最後完成的輸出。

不必要:可以對許多微控制器進行遠程調試。 JTAG或專有協議可用於暫停處理器,查看寄存器和RAM,甚至更改正在運行的處理器的狀態而無需重新編譯。這就是為什麼即使在具有大量空間和功能的PC上,調試器通常也比打印語句更好的調試方法。

不幸的是,對於新手來說,最常見的微控制器平台Arduino沒有調試器。 AVR支持遠程調試,但是Atmel的debugWIRE協議是專有的,沒有記錄。您可以使用官方的開發板進行GDB調試,但是如果您有,那您可能就不必再擔心Arduino了。

您不能使用函數指針來處理正在記錄的內容,並增加整體靈活性嗎?
Adam Davis
2014-04-04 16:30:36 UTC
view on stackexchange narkive permalink

printf()不能單獨工作。它調用了許多其他函數,如果堆棧空間很小,則可能根本無法使用它來調試接近堆棧限制的問題。取決於編譯器和微控制器,格式字符串也可以放置在內存中,而不是從閃存中引用。如果您在代碼中加上printf語句,則可能會加起來很大。在Arduino環境中,這是一個大問題-使用數十或數百個printf語句的初學者突然遇到了看似隨機的問題,因為他們正在用堆棧覆蓋其堆。

儘管我很感謝downvote本身提供的反饋,但是如果不同意的人解釋了此答案的問題,對我和其他人會更有幫助。我們都在這裡學習和分享知識,考慮分享您的知識。
supercat
2014-12-11 02:11:25 UTC
view on stackexchange narkive permalink

即使想要將數據吐到某種形式的日誌記錄控制台中, printf 函數通常也不是一種很好的方法,因為它需要檢查傳遞的格式字符串並進行解析它在運行時;即使代碼從未使用%04X 之外的任何格式說明符,控制器通常也將需要包含解析任意格式字符串所需的所有代碼。根據使用的確切控制器,使用類似以下代碼的代碼可能會更有效:

  void log_string(const char * st){int ch;做{ch = * st ++;如果(ch == 0)中斷; log_char(ch); } while(1);} void log_hexdigit(unsigned char d){d& = 15;如果(d > 9)d + = 7; log_char(d +'0');} void log_hexbyte(unsigned char b){log_hexdigit(b >> 4); log_hexdigit(b); } void log_hexi16(uint16_t s){log_hexbyte(s >> 8); log_hexbyte(s); } void log_hexi32(uint32_t i){log_hexbyte(i >> 24); log_hexbyte(i >> 16); log_hexbyte(i >> 8); log_hexbyte(i); } void log_hexi32p(uint32_t * p)//在指針小於32位的平台上{log_hexi32(* p); }  

在某些PIC單片機上, log_hexi32(l)可能需要9條指令,並且可能需要17條指令(如果 l 位於第二行),而 log_hexi32p(&l)將花費2。 log_hexi32p 函數本身的長度約為14條指令,因此,如果調用兩次,它將為自己付出代價

John U
2014-04-04 13:28:43 UTC
view on stackexchange narkive permalink

其他答案都沒有提到的一點:在基本的微型程序(IE中,只有main()循環,並且可能隨時運行幾個ISR,而不是多線程OS),如果它崩潰了/停止/陷入循環,您的打印功能將不發生

此外,人們還說過“不要使用printf”或“ stdio.h”空間”,但沒有太多替代方法-Embedded.kyle提到了簡化的替代方法,而這正是在基本的嵌入式系統上理所當然應該做的事情。從UART中噴出幾個字符的基本例程可能是幾個字節的代碼。

如果您的printf沒有發生,您已經學到了很多有關代碼存在問題的地方。
假設可能只有一個printf,是的。但是在調用printf()從UART中取出任何東西時,中斷可能觸發數百次


該問答將自動從英語翻譯而來。原始內容可在stackexchange上找到,我們感謝它分發的cc by-sa 3.0許可。
Loading...