題:
Arduino中的全局變量是否有害?
ATE-ENGE
2017-07-11 18:07:12 UTC
view on stackexchange narkive permalink

我在編程領域還比較陌生,我正在閱讀的許多編碼最佳實踐都有效地說明了使用全局變量的充分理由很少(或者最好的代碼根本沒有全局變量)。 p p>

我已盡全力記住這一點,編寫軟件以使用SD卡建立Arduino接口,與計算機交談並運行電動機控制器時。

我目前擁有46個全局變量,用於大約1100行“入門級”代碼(沒有一行執行多個操作)。這是一個很好的比率,還是我應該進一步降低它?

我在這裡問這個問題是因為我特別關注在Arduino產品上編碼的最佳實踐,而不是一般的計算機編程。

>

在Arduino中,您無法避免使用全局變量。函數/方法範圍之外的每個變量聲明都是全局的。因此,如果您需要在函數之間共享值,則它們必須是全局變量,除非您想將每個值都作為參數傳遞。
@LookAlterno Err,因為它只是帶有奇怪宏和庫的C ++,所以您不能在Ardunio中編寫類嗎?如果是這樣,並不是每個變量都是全局變量。即使在C語言中,通常也被認為是最佳做法,而不是使用全局變量,而是更喜歡將變量(也許在結構內部)傳遞給函數。對於小型程序而言,它可能不太方便,但通常會在程序變得更大和更複雜時得到回報。
@Muzer。我僅將C ++類用於庫開發以及大型程序。由於某種原因,我避免使用C ++。
@LookAlterno: _“我避免” _和_“您不能” _是非常不同的東西。
@LookAlterno這是C / C ++的普通說法(儘管有命名空間),它根本不是特定於Arduino的,而且正如您自己所指出的那樣,它絕不會導致無法避免全局變量的結論。他們可以而且經常應該這樣做。當平台是Arduino時,此問題的答案可能會有所不同,這不在語言的層面上,而是在平台上有意義的設計模式以及它們是否適合全局性。
實際上,某些嵌入式程序員禁止使用局部變量,而需要全局變量(或函數作用域的靜態變量)。當程序較小時,可以簡化為簡單狀態機的分析程序;因為變量永遠不會相互覆蓋(即:因為它們確實會堆疊和堆積分配的變量)。
靜態變量和全局變量具有在編譯時知道內存消耗的優點。對於具有非常有限的可用內存的arduino,這可能是一個優勢。對於新手來說,耗盡可用內存並經歷無法追踪的故障非常容易。
Arduino上的全局變量比PC上的邪惡程度小。我見過有人將循環變量i聲明為全局變量。 TBH我嘗試避免使用它們,但是它們確實在內存管理中佔有一席之地。
@LookAlterno-全局變量的最大問題是可憐的程序員。您應該將每個值作為參數傳遞(嘗試將其保持小於5),因為這會將您的代碼分隔開並允許重用。將函數一起復制和粘貼到程序中確實很容易,但前提是要盡可能避免使用全局變量。它還防止單片函數,如果將20個args傳遞給2000行函數,則應該對其進行重構。
@Muzer嗯,一直以為是處理中。瓷磚
相關:https://electronics.stackexchange.com/questions/97241/use-of-global-variables-in-embedded-systems
大家好:這項功能比預期的受歡迎得多。在過去的9個月中,我學到了很多東西,並希望對“我的其他人”盡可能有用。如果我在這篇文章的後面附加了指向教程/思想的鏈接,這些鏈接可以幫助我作為初學者改進代碼,那麼人們會對此表示讚賞嗎?我仍然對堆棧交換的內容沒有100%的信心,所以我想問一問。
八 答案:
Majenko
2017-07-11 18:19:56 UTC
view on stackexchange narkive permalink

它們本身並不是邪惡,但在沒有充分理由使用它們的情況下,它們的確會被過度使用。

在很多情況下,全局變量是有利的。特別是因為對Arduino進行編程與對PC進行編程有很大不同。

全局變量的最大好處是靜態分配。特別是對於大型和復雜的變量,例如類實例。由於缺乏資源,動態分配(使用 new 等)不受歡迎。

此外,您不會像普通C程序那樣得到單個調用樹(單個 main()函數調用其他函數)-相反,您實際上獲得了兩個單獨的樹( setup()調用函數,然後是 loop()調用函數),這意味著有時全局變量是實現目標的唯一途徑(即,如果要在 setup() loop()中使用它) )。

所以不,它們不是邪惡的,在Arduino上,它們的使用比在PC上更多,更好。

好的,如果我只在`loop()`中使用它(或者在`loop()`中調用的多個函數中使用),該怎麼辦?以一種不同於最初定義它們的方式來設置它們會更好嗎?
在那種情況下,我可能會在loop()中定義它們(如果我需要它們在迭代中保留它們的值,則可能是“靜態”),並將它們通過函數參數傳遞給調用鏈。
好的,我想我可以做到。如果我有(例如)在“ loop()”中定義的“ static var n”。如果我將`n`傳遞給`foo(var)`,並在`foo(var)`中設置`n = [new value]`。我是否需要在`loop()`中返回`[new value]`並具有`n = [new value]`?
這是一種方法,或者將其作為參考傳遞:`void foo(int&var){var = 4; }和`foo(n);`-`n`現在是4。
類可以堆棧分配。誠然,將大型實例放在堆棧上不是很好,但是還是可以的。
@JAB可以堆棧分配任何內容。
是的,我跳過得太快了,並將關於不使用班級問題的一些評論混為一談。
“ setup()”和“ loop()”之間無法進行通信這一事實本身就是使用更標準的工具套件並具有良好的“ main()”或“ _main()”入口點的絕佳理由。 。
我聽說避免全局變量與Arduino以外的系統有更多關係的原因之一,全局變量可以暴露在程序本身的外部或外部。此C ++語句最好地說明了這一點。 “ C語言沒有全局關鍵字。在函數外部聲明的變量具有“文件範圍”,這意味著它們在文件中可見。”這使變量空間在運行之前在程序範圍之外被修改,例如靜態情況,可以在運行之前編輯文件變量空間,從而帶來潛在的安全風險
Edgar Bonet
2017-07-11 18:29:32 UTC
view on stackexchange narkive permalink

很難在沒有看到實際代碼的情況下給出明確的答案。

全局變量並不是邪惡的,它們通常在嵌入式環境中有意義,在該環境中,您通常需要進行大量硬件訪問。您只有四個UART,只有一個I2C端口等。因此,對於與特定硬件資源相關聯的變量使用全局變量是有意義的。確實,Arduino核心庫做到了: Serial Serial1 等是全局變量。另外,代表程序全局狀態的變量通常是全局變量。

我目前有大約1100行[code]的46個全局變量。這是一個好比率嗎?[...]

與數字無關。對於每個全局變量,應該問自己一個正確的問題是,將它包含在globalscope中是否有意義。如果其中一些保持恆定值,則將它們限定為 const :編譯器通常會優化其存儲。如果這些變量中的任何一個僅在單個函數內使用,請將其設為局部。如果您希望其值在函數調用之間保持不變,則將其限定為 static 。您還可以通過在一個類中將變量分組在一起並擁有該類的一個全局實例來減少“可見”全局變量的數量。但是只有在有意義的情況下才可以這樣做。為了僅擁有一個全局變量而創建一個大型 GlobalStuff 類將無法使代碼更清晰。

好!如果我有一個僅在一個函數中使用但每次調用一個函數都得到一個新值的變量(例如var = millis()),我就不知道它是否應該成為靜態的?
不可以。“靜態”僅適用於需要保留值的情況。如果您在函數中所做的第一件事是將變量的值設置為當前時間,則無需保留舊值。在這種情況下,static所做的所有事情都是在浪費內存。
這是一個非常全面的答案,表達了支持和懷疑全球化的觀點。 +1
BOC
2017-07-11 20:17:53 UTC
view on stackexchange narkive permalink

全局變量的主要問題是代碼維護。讀取一行代碼時,很容易找到作為參數傳遞或在本地聲明的變量的聲明。很難找到全局變量的聲明(通常需要使用IDE)。

當您有很多全局變量(已經有40個)時,很難找到一個明確的名稱不太長。使用名稱空間是闡明全局變量作用的一種方法。

在C中模仿名稱空間的一種較差的方法是:

 靜態結構{int motor1,motor2;布爾傳感器;}手臂;  

在英特爾或手臂處理器上,對全局變量的訪問比其他變量要慢。 arduino可能與此相反。

在AVR上,全局變量位於RAM上,而大多數非靜態本地變量都分配給CPU寄存器,這使得它們的訪問速度更快。
問題並不在於真正找到聲明。能夠快速理解代碼很重要,但最終僅次於它的工作-真正的問題是找到代碼的哪個未知部分中的哪個varmint能夠使用全局聲明對您遺漏的對象進行奇怪的操作開放給所有人,讓他們隨心所欲。
Andrew
2017-07-11 18:49:12 UTC
view on stackexchange narkive permalink

與所有事物一樣(除了真正的邪惡的goto之外),全局變量都有其位置。

例如如果您有一個調試串行端口或一個日誌文件,需要能夠從任何地方寫入該文件,那麼將其全局化通常是有意義的。同樣,如果您有一些關鍵的系統狀態信息,那麼將其全局化通常是最簡單的解決方案。不必將值傳遞給程序中的每個函數。

正如其他人所說,對於僅超過1000行的代碼而言,46似乎很多,但是卻不知道其中的細節。您正在做什麼,很難說是否過度使用它們。

但是,對於每個全球用戶,請問自己一些重要的問題:

名稱是否明確具體?我不會在其他地方不小心使用相同的名稱嗎?如果不更改名稱。

是否需要更改名稱?如果不是,則考慮使其成為const。

這是否需要在任何地方都可見,還是僅是全局的,以便在函數調用之間保持該值?因此,請考慮使其在函數中本地化並使用關鍵字static。

如果在我不小心的情況下通過代碼更改了代碼,事情真的會糟透了嗎?例如如果您有兩個相關的全局變量(例如名稱和ID號),它們是全局變量(請參見前面有關在幾乎各處都需要一些信息時使用全局變量的說明),則如果其中一個變量被更改而沒有其他麻煩的事情發生。是的,您可能只是小心一點,但有時稍微加強一點謹慎是件好事。例如,將它們放在另一個.c文件中,然後定義可以同時設置它們並允許您讀取它們的函數。然後,您僅將函數包括在關聯的頭文件中,這樣,您的其餘代碼只能通過已定義的函數訪問變量,因此不會弄亂事情。這基本上是做c ++類所允許的一種純粹的c方式,但不必學習如何定義和創建類,這樣您就不會立即學到太多。

-更新-我剛剛意識到您是在詢問Arduino特定的最佳實踐,而不是一般的編碼,而這更多是一個一般的編碼回复。但老實說並沒有太大的區別,好的做法就是好的做法。 Arduino的 startup() loop()結構意味著在某些情況下,您必須比其他平台多使用一點全局變量,但這並沒有太大改變,無論平台是什麼,您總是最終會在平台限制內力求做到最好。

我對Arduino一無所知,但做了很多台式機和服務器開發。 goto有一個可接受的用法(IMHO),它可以打破嵌套循環,比其他方法更乾淨,更容易理解。
如果您認為goto是邪惡的,請查看Linux代碼。可以這麼說,它們和`try ... catch`塊一樣邪惡。
`gotos`不是邪惡的。它們一直用於生產中,用於處理錯誤的專業C代碼,包括在用於救生設備,飛機等的對安全至關重要的實時軟件中。它們僅需正確使用即可,其使用方式是有限且定義明確。另外,為了保持項目符合MISRA的要求,如果這是項目所關心的,則必須將項目中gotos的使用模式記錄為一個例外,這通常是在符合MISRA的軟件中完成的。
這是我寫的一些示例,顯示了合理的“ goto”用法:https://stackoverflow.com/a/54488289/4561887。我在答案的底部添加了其他參考,以證明其用法合理。當然,沒有`goto`就不能完成任何代碼(即_all_代碼_can_不能通過`goto`完成),但是通過正確地使用`goto`可以使很多代碼實例受益匪淺,並使代碼更具可讀性。一些專業團隊甚至要求以標準化方式使用“ goto”,以使代碼更清晰,可讀性更好。這都是關於權衡的。
Michel Keijzers
2017-07-11 19:19:05 UTC
view on stackexchange narkive permalink

儘管在為PC編程時不會使用它們,但對於Arduino,它們有一些好處。

  • 沒有動態內存使用情況(在Arduino的有限堆空間中造成缺口)
  • 隨處可見,因此無需傳遞它們作為參數(佔用堆棧空間)

在某些情況下,尤其是在性能方面,最好使用全局變量,而不是在需要時創建元素:

  • 減少堆空間中的空白
  • 動態分配和/或釋放內存可能需要大量時間
  • 變量可以被“重用”,例如a的元素列表出於多種原因而使用的全局列表,例如一次作為SD的緩衝區,後來作為臨時字符串緩衝區。
Arjen
2017-07-12 18:14:42 UTC
view on stackexchange narkive permalink

他們是邪惡的嗎?也許。全局變量的問題在於,它們可以在任何時間不受執行的任何功能或代碼段的訪問和修改。假設這可能導致難以追溯和解釋的情況。因此,希望將全局變量的數量最小化,如果可能的話,使數量回到零。

是否可以避免?幾乎總是可以。 Arduino的問題在於,它們迫使您採用這兩種功能方法,即假定您 setup() loop()。在這種情況下,您將無法訪問這兩個函數的調用方函數的範圍(可能是 main())。如果有的話,您將可以擺脫所有全局變量,而可以使用本地變量。

生動描述以下內容:

  int main(){setup(); while(true){loop(); } return 0;}  

這大概與Arduino程序的主要功能類似。然後,最好在 main()函數的範圍內聲明 setup() loop()函數中所需的變量而不是全球範圍。然後,可以通過將它們作為參數傳遞給其他兩個函數(如果需要,可以使用指針)來訪問它們。

例如:

  int main(){ int myVariable = 0;設置(&myVariable); while(true){loop(&myVariable); } return 0;}  

請注意,在這種情況下,您還需要更改兩個函數的簽名。

因為這可能不可行也不合乎需要,我看到了實際上,這是從Arduino程序中刪除大多數全局變量而不修改強製程序結構的唯一方法。

如果我沒記錯的話,那麼您在為Arduino編程時完全可以使用C ++,而不是C。如果您還不熟悉 OOP(面向對象編程)或C ++ ,可能需要一些時間來適應和閱讀。

我的建議是創建一個Program類,並創建該類的單個全局實例。

請考慮以下示例程序:

  class程序{public:Program();} void setup(); void loop(); private:int myFirstSampleVariable; int mySecondSampleVariable;}; Program :: Program():myFirstSampleVariable(0),mySecondSampleVariable(0){} void Program :: setup(){//您的安裝代碼在這裡} void Program :: loop(){//您的循環代碼在此處}編程程序; //您的單個globalvoid setup(){program.setup();} void loop(){program.loop();}  

Voilà,我們擺脫了幾乎所有的globals 。開始添加應用程序邏輯的函數是 Program :: setup() Program :: loop()函數。這些函數可以訪問實例特定的成員變量 myFirstSampleVariable mySecondSampleVariable ,而傳統的 setup() loop()函數無權訪問,因為這些變量已被標記為私有。這個概念稱為數據封裝或數據隱藏。

教您OOP和/或C ++有點超出了此問題的答案範圍,所以我在這裡停止。

總結:應該避免使用全局變量,並且幾乎總是可以大幅度減少全局變量的數量。同樣,當您為Arduino編程時。

最重要的是,我希望我的回答對您有所幫助:)

如果願意,可以在草圖中定義自己的main()。這就是股票的樣子:https://github.com/arduino/Arduino/blob/1.8.3/hardware/arduino/avr/cores/arduino/main.cpp#L33-L51
單身人士實際上是一個整體,只是以一種額外的混亂方式打扮。它有同樣的缺點。
@patstew您介意向我解釋一下您有同樣的缺點嗎?我認為並非如此,因為您能夠使用數據封裝來謀取利益。
@per1234謝謝!我絕對不是Arduino專家,但我想我的第一個建議也可以。
嗯,它仍然是全局狀態,可以在程序中的任何位置訪問,您只需通過Program :: instance()。setup()而不是globalProgram.setup()進行訪問即可。將相關的全局變量放入一個類/結構/命名空間可能是有益的,特別是如果僅幾個相關函數需要它們,但是單例模式不會添加任何內容。換句話說,“靜態程序p;”具有全局存儲,而“靜態Program&instance()”具有全局訪問權限,這與“程序globalProgram;”完全相同。
@patstew我沒有那樣想。我想你是正確的。我將修改我的帖子。這也將使它更容易理解。
作為一個長期的嵌入式編碼器,我相當定期地從“全局變量錯誤”的鑄鐵規則中訓練人們。 *總是*傳遞參數會使代碼變慢。在PC上,這幾乎無關緊要,但是在嵌入式世界中,這仍然是很大的事情。訪問器/更改器模式是一種以處理時間為代價保證聲音封裝的方法。用全局變量編寫封裝良好的代碼是完全可能的-但您必須對設計方法應用紀律,並明確指定其他人應如何使用它。
-1
hobbs
2017-07-13 12:25:10 UTC
view on stackexchange narkive permalink

全局變量永遠不會邪惡。反對它們的一般規則只是讓您生存足夠長的時間來獲取經驗,以便做出更好的決定的拐杖。不管我們是在談論可能包含多個事物的全局數組或映射,還是仍然假設只有一個 這樣的列表或映射,而沒有多個獨立的數組或映射) 。

因此,在使用全局變量之前,您想問自己:我可以想使用不止一個這樣的東西嗎?如果事實確實如此,那麼您將必須修改代碼以取消全局化,然後您可能會發現代碼的其他部分依賴於唯一性假設,因此您也將不得不對其進行修復,並且該過程將變得乏味且容易出錯。之所以講授“不要使用全局變量”,是因為通常從一開始就避免全局變量的花費很小,並且避免了以後不得不支付大量費用的可能性。

但簡化的假設是全局變量還可以使您的代碼更小,更快,並且使用更少的內存,因為它不必傳遞使用的是什麼東西的概念,不必進行間接操作,也不必考慮所需的東西可能不存在,等等。在嵌入式系統上,與PC上相比,您更有可能受到代碼大小和/或CPU時間和/或內存的限制,因此這些節省很重要。而且許多嵌入式應用程序的要求也更加嚴格-您知道您的芯片僅具有某個外圍設備,用戶不能僅僅將另一個外圍設備插入USB端口或其他設備中。

需要多個看似獨特的東西的另一個常見原因是測試-僅將某個組件的測試實例傳遞給函數,而試圖修改組件的行為時,測試兩個組件之間的交互會更容易。全局組件是一個棘手的命題。但是嵌入式世界中的測試與其他地方的測試往往有很大的不同,因此這可能不適用於您。據我所知,Arduino沒有任何測試文化。

因此,在它們似乎值得使用時,請繼續使用它們。密碼警察不會來接你。只是知道錯誤的選擇可能會為您帶來更多的工作,因此,如果您不確定...

dannyf
2017-07-13 14:57:29 UTC
view on stackexchange narkive permalink

Arduino中的全局變量是邪惡的嗎?

包括全局變量在內,什麼都不是固有的邪惡。我將其描述為“必不可少的惡魔”-它可以使您的生活更加輕鬆,但應謹慎對待。

我還可以採取哪些措施來進一步減少全球人數?

使用包裝函數來訪問全局變量。因此至少要從範圍的角度進行管理。

如果使用包裝函數來訪問全局變量,則最好將變量放入這些函數中。


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