[英]Variable declaration placement in C
我一直認為在 C 中,所有變量都必須在函數的開頭聲明。 我知道在C99中,規則和C++一樣,但是C89/ANSI C的變量聲明放置規則是什么?
以下代碼使用gcc -std=c89
和gcc -ansi
成功編譯:
#include <stdio.h>
int main() {
int i;
for (i = 0; i < 10; i++) {
char c = (i % 95) + 32;
printf("%i: %c\n", i, c);
char *s;
s = "some string";
puts(s);
}
return 0;
}
c
和s
的聲明不應該在 C89/ANSI 模式下導致錯誤嗎?
它編譯成功是因為 GCC 允許將s
聲明為 GNU 擴展,即使它不是 C89 或 ANSI 標准的一部分。 如果你想嚴格遵守這些標准,你必須通過-pedantic
標志。
{ }
塊開頭的c
聲明是 C89 標准的一部分; 該塊不必是一個函數。
對於 C89,您必須在作用域塊的開頭聲明所有變量。
因此,您的char c
聲明是有效的,因為它位於 for 循環范圍塊的頂部。 但是, char *s
聲明應該是一個錯誤。
由於舊的、原始的 C 編譯器的限制,在塊的頂部對變量聲明進行分組是一種遺留問題。 所有現代語言都推薦,有時甚至強制在最晚的地方聲明局部變量:它們第一次初始化的地方。 因為這消除了錯誤使用隨機值的風險。 分離聲明和初始化還可以防止您盡可能使用“const”(或“final”)。
不幸的是,C++ 一直接受舊的、頂級的聲明方式以與 C 向后兼容(一個 C 兼容性拖出了許多其他人......)但 C++ 試圖擺脫它:
C99 開始在同一個方向上移動 C。
如果您擔心找不到聲明局部變量的位置,那么這意味着您有一個更大的問題:封閉塊太長,應該拆分。
https://wiki.sei.cmu.edu/confluence/display/c/DCL19-C.+Minimize+the+scope+of+variables+and+functions
從可維護性而不是語法的角度來看,至少有三種思路:
在函數的開頭聲明所有變量,這樣它們就在一個地方,您可以一目了然地看到完整的列表。
將所有變量聲明為盡可能靠近它們首次使用的位置,這樣您就會知道為什么需要每個變量。
在最內層作用域塊的開始處聲明所有變量,這樣它們將盡快超出作用域,並允許編譯器優化內存並告訴您是否在您不想要的地方意外使用它們。
我通常更喜歡第一個選項,因為我發現其他選項經常迫使我搜索聲明的代碼。 預先定義所有變量還可以更輕松地初始化和從調試器觀察它們。
我有時會在一個較小的范圍塊中聲明變量,但只是出於一個很好的理由,我很少這樣做。 一個例子可能是在fork()
,聲明只有子進程需要的變量。 對我來說,這個視覺指示器有助於提醒他們的目的。
正如其他人所指出的,GCC 在這方面是寬容的(可能還有其他編譯器,取決於它們被調用的參數),即使在“C89”模式下,除非您使用“迂腐”檢查。 老實說,不學究的理由並不多; 高質量的現代代碼應該總是在沒有警告的情況下編譯(或者很少有你知道你正在做一些對編譯器來說可疑的特定事情作為可能的錯誤),所以如果你不能用迂腐的設置編譯你的代碼,它可能需要一些注意。
C89 要求在每個范圍內的任何其他語句之前聲明變量,后來的標准允許更接近使用的聲明(這可以更直觀和更有效),尤其是在“for”循環中同時聲明和初始化循環控制變量。
如前所述,對此有兩種思想流派。
1) 在函數頂部聲明所有內容,因為年份是 1987。
2) 聲明最接近首次使用並在盡可能小的范圍內。
我的答案是兩者都做! 讓我解釋:
對於長函數,1) 使得重構非常困難。 如果您在開發人員反對子例程概念的代碼庫中工作,那么您將在函數的開頭有 50 個變量聲明,其中一些可能只是 for 循環的“i”函數的底部。
因此,我由此制定了最高聲明 PTSD,並嘗試虔誠地做選項 2)。
我回到選項一是因為一件事:短功能。 如果你的函數足夠短,那么你的局部變量就會很少,而且由於函數很短,如果你把它們放在函數的頂部,它們仍然會接近第一次使用。
此外,當您想在頂部聲明但尚未進行初始化所需的一些計算時,“聲明並設置為 NULL”的反模式已解決,因為您需要初始化的內容可能會作為參數接收。
所以現在我的想法是你應該在函數的頂部聲明,並盡可能靠近第一次使用。 所以兩者! 這樣做的方法是使用划分良好的子程序。
但是如果你正在處理一個長函數,那么把最接近的東西放在最先使用的地方,因為這樣提取方法會更容易。
我的食譜是這個。 對於所有局部變量,取變量並將其聲明移至底部,編譯,然后將聲明移至編譯錯誤之前。 這是第一次使用。 對所有局部變量執行此操作。
int foo = 0;
<code that uses foo>
int bar = 1;
<code that uses bar>
<code that uses foo>
現在,定義一個范圍塊,它在聲明之前開始並移動到程序編譯結束
{
int foo = 0;
<code that uses foo>
}
int bar = 1;
<code that uses bar>
>>> First compilation error here
<code that uses foo>
這不會編譯,因為還有一些使用 foo 的代碼。 我們可以注意到編譯器能夠通過使用 bar 的代碼,因為它沒有使用 foo。 此時,有兩種選擇。 機械的一種是將“}”向下移動直到它編譯,另一種選擇是檢查代碼並確定順序是否可以更改為:
{
int foo = 0;
<code that uses foo>
}
<code that uses foo>
int bar = 1;
<code that uses bar>
如果可以切換順序,那可能就是您想要的,因為它縮短了臨時值的生命周期。
需要注意的另一件事是,是否需要在使用它的代碼塊之間保留 foo 的值,或者它是否可以在兩者中使用不同的 foo。 例如
int i;
for(i = 0; i < 8; ++i){
...
}
<some stuff>
for(i = 3; i < 32; ++i){
...
}
這些情況需要的不僅僅是我的程序。 開發人員必須分析代碼以確定要做什么。
但第一步是找到第一個用途。 您可以直觀地進行操作,但有時,刪除聲明、嘗試編譯並將其放回第一次使用的上方會更容易。 如果第一次使用是在 if 語句中,將它放在那里並檢查它是否編譯。 然后編譯器將識別其他用途。 嘗試制作一個包含這兩種用途的范圍塊。
在這個機械部分完成后,分析數據的位置就變得更容易了。 如果在大范圍塊中使用了變量,請分析情況並查看您是否只是將同一個變量用於兩個不同的事物(例如用於兩個 for 循環的“i”)。 如果用途不相關,則為這些不相關的用途中的每一個創建新變量。
我將引用 gcc 4.7.0 版手冊中的一些陳述以獲得清晰的解釋。
“編譯器可以接受幾個基本標准,例如'c90'或'c++98',以及這些標准的GNU方言,例如'gnu90'或'gnu++98'。通過指定基本標准,編譯器將接受所有遵循該標准的程序以及那些使用與該標准不矛盾的 GNU 擴展的程序。例如,'-std=c90' 關閉 GCC 的某些與 ISO C90 不兼容的功能,例如 asm 和 typeof 關鍵字,但不會其他在 ISO C90 中沒有意義的 GNU 擴展,例如省略 ?: 表達式的中間詞。”
我認為你的問題的關鍵是,即使使用了選項“-std=c89”,為什么 gcc 不符合 C89。 我不知道你的 gcc 的版本,但我認為不會有太大的區別。 gcc 的開發人員告訴我們,選項“-std=c89”只是意味着與 C89 相矛盾的擴展被關閉。 因此,它與某些在 C89 中沒有意義的擴展無關。 並且不限制變量聲明放置的擴展屬於與C89不矛盾的擴展。
老實說,大家第一眼看到“-std=c89”這個選項都會認為它應該完全符合C89。 但事實並非如此。 至於一開始就聲明所有變量是好是壞的問題只是習慣問題。
您應該在函數的頂部或“本地”聲明所有變量。 答案是:
這取決於您使用的系統類型:
1/ 嵌入式系統(特別是與飛機或汽車之類的生活相關):它確實允許您使用動態內存(例如:calloc、malloc、new...)。 想象一下,你正在一個非常大的項目中工作,有 1000 名工程師。 如果他們分配新的動態內存並忘記刪除它(當它不再使用時)怎么辦? 嵌入式系統長時間運行會導致堆棧溢出,軟件損壞。 不容易保證質量(最好的辦法是禁用動態內存)。
如果飛機在 30 天內運行並且沒有關閉,如果軟件損壞(當飛機仍在空中時)會發生什么?
2/ 其他系統如web、PC(內存空間大):
您應該“本地”聲明變量以優化使用的內存。 如果這些系統運行時間長了會發生堆棧溢出(因為有人忘記移除動態內存)。 只需做簡單的事情即可重置 PC :P 對生活沒有影響
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.