題:
通常如何在arduino和AVR架構中管理錯誤(與語法無關)?
Coder_fox
2018-11-29 01:39:23 UTC
view on stackexchange narkive permalink

我只是對AVR架構如何管理將導致常規桌面程序崩潰的錯誤感到好奇。我說的是邏輯錯誤,例如未定義的數學問題。例如除以0並得到負數的平方根。起初,我期望通過給AVR芯片一個錯誤來返回0。但是我運行了這個程序:

  void setup(){Serial.begin(9600);} void loop(){int x = 0; int p = 50; int結果;結果= p / x; Serial.println(結果); result = sqrt(-10); delay(1000); Serial.println(result); delay(10000);}  

並獲得以下輸出:

  42949672950  

在此之後,我感到非常困惑。為什麼 result 變量取一個 unsigned long 的最大值?自從我聲明了 int 以來,微控制器必須為此變量專用2個字節的動態內存,但是顯然,它設法以某種方式設法獲得了額外的2個字節來存儲數據。這是否意味著AVR芯片可以破壞其他存儲器位置中的數據,同時為剛剛被饋入未定義變量的變量分配新數據?好的,也許AVR芯片只是將任何數學上的廢話都設置為4294967295。但是在獲得-10的平方根的示例中,我們沒有看到該值變為0。這些錯誤的保護措施。另外,我嘗試運行上面的程序,但對 result 使用了 byte 變量,而不是 int

  void setup(){Serial.begin(9600);} void loop(){int x = 0; int p = 50; int result; result = p / x; Serial.println(result); result = sqrt( -10); delay(1000); Serial.println(result); delay(10000);}  

,結果是相同的。因此,有一些關於AVR中錯誤管理的文檔,因為在開發過程中了解這一點很重要。

P.S。一切都在arduino nano上的真實Atmega 328p芯片上運行。

我認為標準是整數數學未定義。浮點符合IEEE標準,並返回nan,但也不例外。不可能在Arduino上捕獲例外(我認為):https://stackoverflow.com/questions/10095591/enable-exceptions-in-the-arduino-environment當您發現帶有字節的奇怪東西時,請提供完整的信息草圖,我們可以嘗試告訴我們您使用哪種arduino板。
@Jot這是一個有趣的問題,但是為什麼要用4294967295代替nan。顯然它們沒有相同的二進制值。為什麼Atmel在其處理器體系結構中不包含nan值(可能會通過熔斷器禁用)。這是一項有用的故障排除功能,在所有主要的編程語言中都大量使用。
@Jot我在板上使用了帶有Atmega 328p的Arduino nano。
整數不能為浮點數指定的'nan'。請使用電路板和微控制器以及完整的草圖來更新您的問題。
我嘗試自己繪製草圖,但結果卻有所不同。請顯示您的草圖,並告訴我們您顯示的草圖的結果。您是否正在模擬器中運行此程序?
@Jot不,一切都在真實的arduino上運行。
感謝您提供完整的草圖。我已經添加了答案。您遇到了一個非常奇怪的編譯器錯誤,該錯誤導致4294967295。現在我明白了為什麼我的代碼沒有顯示該數字而您的代碼卻顯示了該數字。
二 答案:
Edgar Bonet
2018-11-29 02:36:52 UTC
view on stackexchange narkive permalink

一個簡單的答案是:根本不處理它們。

根據C和C ++標準,您調用的稱為 未定義行為,表示任何事情都可能發生。在實踐中,這意味著編譯器可以進行優化,前提是您永遠不會調用未定義的行為,並且完全不理會如果假設不成立的情況。生成的代碼可能會完全損壞,這不是編譯器的錯。

AVR架構無法進行除法運算,也無法進行浮點運算。因此,這裡的所有錯誤都發生在軟件級別。請注意, sqrt(-10)不是錯誤(它是NaN,由avr-libc正確處理),但會將其轉換為int(當您將其分配給 result )是一個錯誤。

如果您真的想知道每個錯誤的詳細信息,則只有一個選擇:您必須分解可執行文件程序並閱讀程序集清單。

_“ ...未定義的行為,意味著任何事情都可能發生。” _-值得強調的是,這完全是C / C ++,與AVR芯片無關。完全相同的警告適用於在Intel i5上運行的代碼。
同樣值得注意的是,儘管C ++標準聲明編譯器可以做任何事情,但是C ++編譯器通常定義了C ++規範之外的其他行為。例如,Visual Studio的編譯器根據對體系結構的了解,定義了C ++規範中未定義的x / 0行為。當您想執行規範禁止的操作但您的特定編譯器允許(例如gcc擴展名)時,此功能非常有用。
Jot
2018-11-29 12:40:48 UTC
view on stackexchange narkive permalink

關於數字4294967295

令我驚訝的是,該草圖確實生成了數字4294967295。這不行。
事實證明,編譯器知道這些數字。並嘗試自己做數學(而不是運行時)。

在運行時, 50/0 生成有符號整數-1。
當一個字節為使用 50/0 的結果為255。
該計算的結果是一個只有1的變量。對於16位變量,該值為0xFFFF。對於無符號整數,結果為65535;對於有符號整數,結果為-1;對於無符號字節,結果為255。

到目前為止,還可以。

但是,當編譯器嘗試使用 50/0 進行數學運算時,會將結果轉換為32位變量0xFFFFFFFF。編譯器不會將數學運算放入二進制結果中,而是直接使用32位無符號長0xFFFFFFFF(即4294967295)調用Serial.println()。

原因不是變量類型變量' result ',但除以' p / x '。編譯器認為用兩個16位整數完成數學運算時,最好使32位整數。

我認為,當編譯器進行優化時,結果應與運行時計算相同。
此答案下面有一個有趣的討論。 @EdgarBonet稱編譯器的行為很奇怪,但不是錯誤。

下面是一個測試草圖,它為Arduino Uno用Arduino 1.8.7生成了4294967295: ){Serial.begin(9600);} void loop(){int x = 0;整數p = 50;結果結果= p / x; Serial.println(結果); delay(5000);}

當計算“ x”或“ p”時,例如,來自函數的結果,或生成三個變量之一時,代碼將輸出正常結果易揮發的。

編譯器對未定義行為的處理是一個錯誤,因為按照定義,根據語言標準,編譯器所做的任何事情都是可以接受的。
@EdgarBonet我認為它應該以相同的方式執行任何操作。在您看來,編譯器是否可以在除以零時插入代碼以調音?我不這麼認為。我認為將int更改為unsigned long太奇怪了。
儘管編譯器可能不會_deliberately_做愚蠢的事情,但是當您調用未定義的行為時,確實會發生奇怪的事情。例如,請參閱[此博客文章](http://blog.llvm.org/2011/05/what-every-c-programmer-should-know_14.html)的開頭(整個文章系列必讀適用於任何C或C ++程序員)。奇怪的?是。蟲子?沒有。
@EdgarBonet,謝謝,讀起來很有趣。他們提到的事情很簡單。這裡發生的是在運行時調用某個函數,並且當編譯器對其進行優化時,將調用另一個(重載)函數。那是進一步變得怪異的一步。
_“您認為編譯器可以在除零時插入代碼來吹奏曲調?我不這麼認為。” _-調用未定義行為時,一切都會進行,包括意外大的變量,吹哨或格式化硬駕駛。歡迎來到C和C ++的奇妙世界。有趣的是,如果沒有UB,是否會發生相同的“變量提升”。
僅當x為零且p為非零時才會發生。打印4294967295時,sizeof(result)仍為2。


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