為什麼 C 語言被設計成函數需要先聲明才能被使用?
或許是為了讓編譯器的設計最簡化、編譯花費的時間最少、調用關係最明確等等?
我用過的語言不多,但是在用過了 Python 後,感覺定義完一個函數之後還要專門為它寫一個函數聲明到 .h 文件中供其他 .c 調用時使用真是很蛋疼。從我所能見,讓編譯器增加一步預編譯,以及部分引入類似 Python 的 import 機制,以減少人工的重複勞動,似乎不會帶來什麼弊端。
首先,回答題主的問題,給C語言增加一次預編譯可不可解決這個問題,答案是可以的。
再說為什麼不預編譯:
首先,C語言出現的很早,那時候編譯器也是一個很複雜的東西,當時計算機的內存、外存都很小,編譯器做的太大也是一個麻煩的事情,所以事先聲明就成為一種規範,保留下來,目的是為了讓編譯器更簡單,雖然這一切已經很過時了。
其次,預編譯的成本很高,與腳本語言、解釋語言不同,C語言項目的規模可以很大,比如操作系統一級的C語言工程,其源文件有幾萬個,涉及全局符號幾十萬個,這樣規模的項目預編譯一次的負擔是很高的,如果是整個項目完全掃描一遍,遍歷所有全局符號,再進行真正的編譯,估計很多碼農都會瘋了,等待時間會特別長。
再次,C語言是一種靜態鏈接的語言,如果一個項目被設計成只編譯,不鏈接的方式,比如有些庫就會被設計成這樣,有些合作開發的項目里,組員之間有時候也只提供obj文件,那麼某些全局符號可能就不包含在現有的代碼里,那麼預搜索就一定找不到某些符號,那麼該怎麼辦?如果不提供聲明,這個代碼就沒辦法編譯了。
基於以上幾點考慮,所以C語言才設計成這樣,對於開發者而言,不算友好,但也不算很糟糕,甚至在某些方面是有好處的。
大概就是這樣吧。你是沒用過 PASCAL 啊……這玩意有個 forward 關鍵字專門用來前置聲明。其核心問題是——如果允許後置聲明那麼編譯就要多一個 pass,在 PDP 那會估計就是不短的一段時間。
其實在ANSI C之前C語言是不需要聲明函數就能調用的。
在編譯階段,調用沒有聲明的函數會把這個問題拖延到鏈接階段,在鏈接階段找不到符號才由鏈接器報錯。那麼沒有聲明的情況下怎麼知道這個函數到底是怎樣的原型呢……他們把這叫做autoprototype,根據調用時傳入的形參的類型猜測函數的原型。實際上在ANSI C中你還是不需要完整的聲明原型,可以在聲明的時候把參數列表忽略掉,讓編譯器去根據調用處猜測函數的原型。不過這樣寫出來之後根據不同的調用約定有時候會由於編寫者的錯誤而意外的破壞棧幀。
這樣對許多人來說還是留下一個問題:古老的Turbo C並不遵循ANSI C的建議,它怎麼需要提前聲明函數?老實說Turbo C是一個特例。我相信貝爾實驗室那幫發明了C和UNIX的人都會認為Turbo C的這個設計很無聊,不符合使用的習慣以及實際的需要。
先喊出大招名再出招是比較流行的做法√
我猜是歷史原因造成的。函數聲明不是必需的,編譯器會猜一個原型出來——當然,使用這個猜測原型會讓調用出錯的。
主要在於效率.
為什麼 C 需要聲明.
在 C 里函數調用基本對應硬體指令, 需要處理參數壓棧(順序, 類型長度, 或寄存器傳遞), 返回值傳遞(返回類型, 棧還是寄存器傳遞)等各種問題. 不同的函數, 處理方式不一, 所以需要提前聲明. 根據調用方式自動推導函數類型可能不一致, 工程上可能導致各種難以調試的BUG.
為什麼 Python 之類的可以不需要聲明
在 Python 一個函數調用背後對應的指令非常多, 大概是先從函數名查找函數(找不到時拋異常), 將調用參數統一成數組(tuple)和字典, 傳遞給查找到的函數, 返回值也是統一的(object).
C 語言函數和硬體實現最近, 主要是為高效率. 如果不計效率, 模仿動態語言, 約定所有函數的類型一致, 將函數按名稱註冊到一個全局變數里, 只有一個函數作為入口, 自己在函數里, 按名查找函數, 解析參數, 這樣也不需要函數聲明了.
示例偽代碼(可以看作是 Python 之類動態語言里函數調用的一種可能實現):
map&
void add_func(name, func) {
g_funcs[name] = func;
}
void call_func(name, data) {
g_funcs[name](data);
}
純粹是歷史原因,C出來的時候編譯器還做不到預掃描後來就約定俗成了而且,總是有喜歡底層開始重新造輪子的人
理論上,完全可以做到不聲明,直接使用。
但C語言非常古老,跟現在的高級語言相比,自然有很多原始的地方。
對函數進行聲明的好處是,能夠簡化編譯器的開發。
在最早的KR標準里,函數的聲明不需要聲明函數的參數,只需要告訴編譯器函數名和返回值就可以了。
這樣的話,在編譯器中,函數名被當做一個指針,對編譯器來說,聲明一個函數跟聲明一個指針變數沒什麼區別,這樣就減少了開發編譯器的工作。
在後來的ANSI C中,標準制定者本著盡量兼容KR和盡量減少改動的前提,保留了函數聲明,只是嚴格了函數聲明的條件,即必須把函數參數類型在聲明中明確標出。
制定ANSI C標準的時候,是唯一的取消C函數聲明的機會,我們失去了。今後也不會再有這樣的機會了,C語言中的函數聲明將與我們相伴終生。
謝邀。確實沒有必要一定這樣。
但是要說這樣做的好處的話,也有。就是當你發布一個三方庫的時候,可以把可鏈接的.so文件和頭文件發送給使用方,暴露介面而隱藏實現(Java里就得額外寫doc了)。當你的頭文件不變的時候,庫更新不會導致符號表變化,所以依賴你庫的程序不需要重新編譯——實際上這個好處在大型c/c++系統中還相當重要。
媽蛋七夕晚上還在知乎答題我這是有多苦逼。。。其實先聲明函數原型是有很大好處的,這個可能和早期的編譯器設計有關。
歷史上,C語言函數並非一定需要聲明,有些函數可以不聲明。當然最好聲明其次,聲明是為了編譯。不正確地聲明,也不可能正確地編譯,甚至無法編譯。樓主似乎有一個誤區,就是以為源文件都是同時編譯的,其實不是的。不同時編譯時,這個聲明的必要性是顯而易見的
C是靜態語言, 且支持多文件分別編譯,最後再鏈接成程序.如果不先看到函數的聲明就調用, 可能會搞錯參數的實際類型和個數.
模塊之間的引用啊假設模塊又是分開實現的
頭文件只是一個聲明文件,而聲明確是程序查找事務的最初索引。舉個簡單例子,你要見網友,見面之前要知道他的基本特徵吧?知道了才能找到真正的人。
可以不聲明,但是不同廠家的編譯器可能會出問題,所以最好還是聲明了。在我大ST的編譯器上我就從來不聲明,不過後來在broadcast的編譯器上吃虧了,後來就不管在哪都聲明了。
仔細想一下,在靜態鏈接過程中,靜態庫文件的符號表裡,保存的是只是函數的地址,那麼當你的自己程序在引用這個靜態庫文件的時候,必須加上個頭文件才行,也就是所謂的聲明了.....你想想如果沒有這個頭文件,編譯器怎麼判斷,你的引用的靜態庫中的函數簽名是對還是錯呀!再智能的編譯器也判斷不出來啊!!由於c/c++編譯鏈接機制本身的限制! 先聲明再使用,那是必須的!
推薦閱讀:
※C# 或者 SQL Server 生成的 GUID 有沒有可能重複?
※為什麼多數遊戲服務端是用 C++ 來寫呢,是歷史原因還是性能方面的考慮?
※程序猿如何快速高效的改 bug?改bug都有哪些技巧?
※怎麼從編程語言的角度解釋kan extension?
※為什麼 2010 年前後誕生的語言(如 Golang, Rust, Swift)都是強類型 + 靜態?