作者tinlans ( )
看板C_and_CPP
標題Re: [問題] global variable的問題
時間Wed Jun 9 23:58:04 2010
※ 引述《Trumen (真好多人)》之銘言:
: http://delphi.ktop.com.tw/board.php?cid=168&fid=912&tid=29349
: 找到問題所在了,分享一下~
: =========================================================================
: 原作者並沒有把他的Code List出來, 只說了他用了Global Value.
: 因此, 以這種描述, 有兩種可能的code.
: a.h, case 1
: static int A;
: static int B;
: sttaic int C;
: a.h, case 2:
: int A;
: int B;
: int C;
: 但是, 因為你把變數宣告在 include file 裡.
不對,
上面兩種 case 通通都是宣告+定義,
慣例上簡稱為定義,
但不能簡稱為宣告。
重複宣告只要每次宣告都一致,
無論幾次都可以:
extern int A;
extern int A;
extern int A;
int A;
上面這樣叫宣告三次、定義一次,
編譯和連結都不會發生問題。
宣告只是告訴 compiler 如何看待那個識別字,
譬如你寫 A = 1 但 A 的 type 並未宣告,
compiler 就不知道它是整數還是浮點數,
因為整數和浮點數的表示形式不同。
如果 A 是 double 型別,
compiler 還必須先把 1 轉成 double 的 1.0。
把宣告抽到 header 只是維持宣告的一致性,
以變數宣告來說,
這麼做是避免某個變數在不同編譯單元被不同的方式解讀。
: 要知道, # 開頭的 command 都會被 preprocessor 處理掉, 因此, 假設你寫的是:
並不是 # 開頭都會,
像是 #pragma 就會留到 compiler 那邊才處理,
當然這是題外話。
: A.cpp:
: #include "a.h"
: int main(void)
: {
: ...
: return 0;
: }
: B.cpp:
: #include "a.h"
: void somefunc(void)
: {
: ...
: }
: 在 a.h case 1 裡, preprocessor 將會把程式展開像這樣:
: a.cpp:
: static int A;
: static int B;
: sttaic int C;
: int main(void)
: {
: ...
: return 0;
: }
: b.cpp:
: static int A;
: static int B;
: sttaic int C;
: void somefunc(void)
: {
: ...
: }
: 在 a.h case 2 裡, preprocessor 將會把程式展開像這樣:
: a.cpp:
: int A;
: int B;
: int C;
: int main(void)
: {
: ...
: return 0;
: }
: b.cpp:
: int A;
: int B;
: int C;
: void somefunc(void)
: {
: ...
: }
: 這兩個有什麼不一樣呢?
: 注意到那個 static 嗎? 根據C/C++語言規則, 定義為 static variable 的變數, 可視區
: 域僅限於宣告區域, 但是存在的壽命卻相當於 external variable 變數.
: 如果我沒記錯的話, static 變數的記憶空間配置是在程式載入初始時期 (就是 main()
: 被執行之前), 而他的內含值設定則是在該敘述第一次被執行到的時候, 且, 也只會設這
: 麼一次, 如果沒有指定特殊值, 則會被設定為零. 且這個記憶體將會保證在離開 main()
: 之前會一直都是存在的.
上面這段文字的部分講的是 static local variable,
但是上面他列的 code 顯然是 global variable,
所以顯然說得不對。
這人把 lifetime、scope、linkage 全部混在一起講,
而且還講錯。
所以奉勸你還是乖乖唸書比較實在,
網路上的別人講的東西幾乎都沒被驗證過。
加上真正懂的人也未必在回你文的時候狀態很好,
有時不小心打錯幾個字,
回文的時候漏看部分引文,
或是往前修文的時候不小心斷開原本應該連貫的東西,
都會造成你很大的誤會。
: 而另一種寫法叫作 external variable. 根據 C/C++ 語言規則, 凡是 external
: variable 變數, 其空間配置和內容初始化時期是在 main() 被執行前, 如果沒有指定特
: 殊值, 則會被設定為零. 且這個記憶體將會保證在離開 main() 之前會一直都是存在的.
: 如果是 external variable, 則無論你宣告多少次, 都應該只會有一份實體存在. 換句話
只要是 global variable 就是這種特性,
跟什麼 static/extern 這種 linkage 特性無關。
: 說, 你在 external variable 可視的領域內作了任何修改, 都應該反應到同一個記憶體
: 空間才對.
這邊才勉強算是說對。
: 但是正常來說, external variable 大都宣告在實作檔 (.c/.cpp) 中, 然後在 header
: file 中宣告 extern type var_name, 然後含入這個 headrer file, 以便讓 compiler
: 知道有這一個 external variable. 或是說讓這個 external variable 的視野擴張到這
: 個實作檔裡.
帶有 external linkage 性質的 global variable 大都「定義」在實作檔中。
而且不是打了 extern 就代表純宣告,
一旦你給了初值又會變成定義,
如:
extern int A = 1;
現在的 compiler 會針對這種 case 發 warning,
因為正常來說不會這樣寫。
: 如果說像 case 2 那樣寫, 沒有宣告 extern, 而是都宣告成 external variable 的話.
: 當編譯時期, compiler 只要看得到reference symbol 就好, 他才不管實際空間位置配置
: , 所有實作檔中, symbol 是否重覆等問題. (只要同一個實作檔中沒有衝突, compiler就
: 不管)
case 2 那個單純就是因為兩個檔案都放了定義,
這段第一行不知道他在扯什麼。
: 前面說的問題是 linker 要去處理的. 因此, 理論上 case 2 的寫法, 應該會引發 link
: time error (或是 Warning, 這可能要看 error level 的設定, 或是 linker 的實作判
: 定).
對 C++ 來說一定是 linking time error,
如果是 C 則完全不可能 error。
case 2 的狀況在 C 語言來說,
它是屬於 common variable (可用沒設初值判定),
A B C 三個 symbol 在外部符號表中會帶 common 屬性,
所以不會發生問題。
雖然這也是題外話,
但因為這個人前面一直講 C/C++,
所以我還是稍微在這裡區別一下。
: 所以我直覺認為, 原作者應該是採用 case 1 的寫法才對. 因為 case 2 的作法, 如果沒
: 有任何錯誤 (duplication symbol 一類的錯誤), 至少應該會有警告.
: 我實在不太相信 borland c++ 會這麼蠢, 連個警告都不給才是. (要是真的, 那我真的無
: 話可說...)
: case 1 的寫法, 你每一個 golbal variable 事實上都是不同的實體, 所以
: compiler/linker都不會提出任何質疑.
這邊沒問題,
都是不同實體,
所以值也沒辦法在編譯單元之間傳遞。
: 不信的話你可以去dump symbol, 他們應該都會被冠上一些奇怪的前置名稱才對. (為了避
: 免linker出來叫)
dump 出來未必有前置名稱,
通常有前置名稱的是 static local variable。
帶前置名稱通常只是為了要避開跟 static global variable 衝到名稱,
當然也可以幫 static global variable 冠一些前置名稱來避免衝到,
這都是做法上的自由。
現在比較實際的做法是讓那個 symbol 帶 local 屬性,
這樣 linker 就算看到不同 object file 裡有相同名稱也不會有事。
: =============================================================================
: 所以問題是在*.cpp include "main.h"時 main.h有global變數
: 使得所有*.cpp都會宣告該變數
: 所以在main.h內使用extern宣告變數的話,變數就不會重複定義了
因為 main.h 你放了定義而不是純宣告,
所以才會變成重複定義。
: 而 static 本來就是 local 的,只因為它放在整個 *.cpp 的 global 區,但它
: 也只限此 *.cpp 使用,對於別的 *.cpp 有同樣的宣告並不會有任何影響。
可以這樣說。
: 以下是我的問題:
: 1. 如果用static宣告在main.h裡的變數的話
: 是否會變成所有*.cpp都會產生static變數呢? 這樣是不是有點占空間阿?
當然會。
: (我只想要在main.cpp有此變數,用static寫法妥當嗎?)
只在 main.cpp 用,
那就用 static,
但是請寫在 main.cpp 裡面。
不讓其它編譯單元知道的東西你擺在 header file 幹嘛。
: (因為我用extern會發生一些奇怪的問題...只能用static)
我覺得單純是你沒分清宣告跟定義,
又靠著用錯誤嘗試法和一些不可靠的資料來解決基礎問題,
才會出現所謂奇怪的問題。
使用工具去解決問題的方法沒有絕對的對或錯之分,
所以可以去翻翻討論區或是互相討論。
但是工具基礎使用方法卻是對錯分明 (像是英文第三人稱單數後面的動詞變化),
查說明書或教科書會是最簡單又有效而穩固的。
總不能為了不想搞懂第三人稱單數後面的動詞變化,
就乾脆以後第三人稱永遠用複數而不用單數。
這樣雖然問題表面上是解決了,
但是以後你句子給別人的感覺就是會很奇怪。
雖然這種比喻看似誇張,
但在寫程式的圈子裡用這種方式去迴避基礎問題的人卻超乎想像的多,
特別是那些解法相當容易在網路上流傳。
: 2. (跟global variabla無關的問題)
: 如果main.h 有#include "function.h"
: 則如果我把 function.h改成function.cpp
: 在main.h 變成#include "function.cpp" 前後會有什麼差嗎?
宣告:
void foo();
定義:
void foo()
{
}
你把定義放在 function.h,
那有兩個 .cpp 去 include 它就穩死。
當然你也可以用 static 甚至是 inline 去解。
可是用了 static 就沒必要放在 header,
而 inline 則是為了改進 performance 才會用到。
如果只是為了解決衝 symbol 的問題去用 inline,
奇怪的問題又可能會發生。
--
Ling-hua Tseng (uranus@tinlans.org)
Department of Computer Science, National Tsing-Hua University
Interesting: C++, Compiler, PL/PD, OS, VM, Large-scale software design
Researching: Software pipelining for VLIW architectures
Homepage:
http://www.tinlans.org
--
※ 發信站: 批踢踢實業坊(ptt.cc)
◆ From: 118.160.110.23
※ 編輯: tinlans 來自: 118.160.110.23 (06/09 23:59)
推 loveflames:如果#pragma被preprocessor處理,那message就印不出巨 06/10 00:01
→ loveflames:集內容了 06/10 00:01
推 VictorTom:推....<(_ _)> 06/10 00:05
推 CrBoy:息怒息怒<(_ _)> 小弟很喜歡您這篇文章!特別是學習觀念部分 06/10 00:15
→ loveme00835:推推~ ^^ 06/10 00:17
推 loveflames:static local在執行main之前先配置空間,在函式第一次 06/10 02:09
→ loveflames:執行才初始化,這樣講沒錯吧? 06/10 02:09
推 DigiPrince:說得太好了! 06/10 06:40