簡體   English   中英

為什么沒有參數的函數(與實際函數定義相比)會編譯?

[英]Why does a function with no parameters (compared to the actual function definition) compile?

我剛剛遇到了某人的 C 代碼,我對它為什么要編譯感到困惑。 有兩點不明白。

  1. 與實際的函數定義相比,函數原型沒有參數。

  2. 函數定義中的參數沒有類型。


#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

為什么這樣做? 我已經在幾個編譯器中對其進行了測試,並且運行良好。

所有其他答案都是正確的,但只是為了完成

函數的聲明方式如下:

 return-type function-name(parameter-list,...) { body... }

return-type是函數返回的變量類型。 這不能是數組類型或函數類型。 如果未給出,則假定為 int

function-name函數的名稱

參數列表是函數采用的參數列表,以逗號分隔。 如果沒有給出參數,則該函數不接受任何參數,應使用空括號或關鍵字 void 進行定義。 如果參數列表中的變量前面沒有變量類型,則假定為 int 數組和函數不會傳遞給函數,而是自動轉換為指針。 如果列表以省略號 (,...) 結尾,則沒有設定數量的參數。 注意:使用省略號時,標頭 stdarg.h 可用於訪問參數。

再次為完整起見。 來自 C11 規范 6:11:6 (第 179 頁)

使用帶空括號的函數聲明符(不是原型格式參數類型聲明符)是一個過時的特性

在 C 中func()意味着您可以傳遞任意數量的參數。 如果你不想要參數,那么你必須聲明為func(void) 您傳遞給函數的類型(如果未指定)默認為int

int func(); 是從沒有 C 標准的日子開始的過時函數聲明,即K&R C的日子(在 1989 年之前,即第一個“ANSI C”標准發布的那一年)。

請記住, K&R C沒有原型,關鍵字void尚未發明。 你所能做的就是告訴編譯器一個函數的返回類型 K&R C 中的空參數列表意味着“未指定但固定”數量的參數。 Fixed 意味着您每次必須使用相同數量的 args 調用函數(而不是像printf這樣的可變參數函數,每次調用的數量和類型都可能不同)。

許多編譯器會診斷這個結構; 特別是gcc -Wstrict-prototypes會告訴你“函數聲明不是原型”,這是正確的,因為它看起來像一個原型(特別是如果你被 C++ 毒害了!),但不是。 這是一個舊式的 K&R C 返回類型聲明。

經驗法則:永遠不要將空參數列表聲明留​​空,使用int func(void)來具體說明。 這將 K&R 返回類型聲明轉換為適當的 C89 原型。 編譯器很高興,開發人員很高興,靜態檢查員很高興。 那些被 ^W^Wfond of C++ 誤導的人可能會畏縮,因為他們在嘗試練習外語技能時需要輸入額外的字符:-)

  • 空參數列表意味着“任何參數”,所以定義沒有錯。
  • 缺少的類型假定為int

我會認為任何通過這個的構建都缺乏配置的警告/錯誤級別,但允許實際代碼沒有意義。

它是K&R風格的函數聲明和定義。 來自 C99 標准 (ISO/IEC 9899:TC3)

第 6.7.5.3 節函數聲明符(包括原型)

標識符列表僅聲明函數參數的標識符。 作為該函數定義的一部分的函數聲明符中的空列表指定該函數沒有參數。 不屬於該函數定義的函數聲明符中的空列表指定不提供有關參數數量或類型的信息。 (如果兩種函數類型都是“舊式”,則不會比較參數類型。)

第 6.11.6 節函數聲明符

使用帶空括號的函數聲明符(不是原型格式的參數類型聲明符)是一個過時的特性。

第 6.11.7 節函數定義

使用具有單獨參數標識符和聲明列表(不是原型格式參數類型和標識符聲明符)的函數定義是一個過時的功能。

舊風格意味着K&R風格

例子:

聲明: int old_style();

定義:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}

如果在函數返回類型和參數列表上沒有給出類型,C 假定為int 只有遵循奇怪的事情的這條規則才是可能的。

函數定義如下所示。

int func(int param) { /* body */}

如果它是你寫的原型

int func(int param);

在原型中,您只能指定參數的類型。 參數的名稱不是強制性的。 所以

int func(int);

此外,如果您不指定參數類型但名稱int被假定為類型。

int func(param);

如果你走得更遠,跟隨也有效。

func();

當您編寫func()時,編譯器假定為int func() func() 但是不要將func()放在函數體內。 那將是一個函數調用

正如@Krishnabhadra 所說,之前其他用戶的所有回復都有正確的解釋,我只是想對一些要點進行更詳細的分析。

在 Old-C 和 ANSI-C 中的“無類型形式參數”,取你的工作寄存器或指令深度能力(影子寄存器或指令累積周期)的維度,在 8 位 MPU 中,將是一個 int16,在一個 16 位MPU 等將是 int16 等,在 64 位架構可能選擇編譯選項的情況下,如:-m32。

雖然在高層看起來實現起來更簡單,但對於傳遞多個參數,程序員在控制維度數據類型步驟的工作變得更加苛刻。

在其他情況下,對於某些微處理器架構,ANSI 編譯器定制,利用一些舊特性來優化代碼的使用,迫使這些“無類型形式參數”的位置在工作寄存器內部或外部工作,今天你得到與使用“volatile”和“register”幾乎相同。

但應該注意的是,最現代的編譯器,不會對這兩種類型的參數聲明做任何區分。

linux下gcc編譯示例:

主文件

main2.c

main3.c
無論如何,在本地聲明原型是沒有用的,因為沒有調用沒有參數引用這個原型將被遺漏。 如果使用帶有“無類型形式參數”的系統,對於外部調用,繼續生成聲明性原型數據類型。

像這樣:

int myfunc(int param);

關於參數類型,這里已經有正確的答案,但是如果您想從編譯器那里聽到它,您可以嘗試添加一些標志(無論如何,標志幾乎總是一個好主意)。

使用gcc foo.c -Wextra編譯你的程序我得到:

foo.c: In function ‘func’:
foo.c:5:5: warning: type of ‘param’ defaults to ‘int’ [-Wmissing-parameter-type]

奇怪的是-Wextra沒有為clang捕捉到這個(由於某種原因,它無法識別-Wmissing-parameter-type ,也許是因為上面提到的歷史原因)但是-pedantic確實:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

對於上面再次提到的原型問題, int func()指的是任意參數,除非您明確地將其定義為int func(void) ,然后會按預期給出錯誤:

foo.c: In function ‘func’:
foo.c:6:1: error: number of arguments doesn’t match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function ‘main’:
foo.c:12:5: error: too many arguments to function ‘func’
foo.c:5:5: note: declared here

或在clang中:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.

如果函數聲明沒有參數,即為空,則它采用未指定數量的參數。 如果您想讓它不帶任何參數,請將其更改為:

int func(void);

這就是為什么我通常建議人們編譯他們的代碼:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

這些標志強制執行以下幾件事:

  • -Wmissing-variable-declarations:如果不先獲得原型,就不可能聲明非靜態函數。 這使得頭文件中的原型更有可能與實際定義匹配。 或者,它強制您將 static 關鍵字添加到不需要公開可見的函數中。
  • -Wstrict-variable-declarations:原型必須正確列出參數。
  • -Wold-style-definition:函數定義本身也必須正確列出參數。

許多開源項目也默認使用這些標志。 例如,FreeBSD 在 Makefile 中使用 WARNS=6 構建時啟用了這些標志。

在舊式聲明符中

除非在函數定義(Par.A.10.1)的頭部使用了聲明符,否則標識符列表必須不存在。 聲明沒有提供關於參數類型的信息。 例如,聲明

int f(), *fpi(), (*pfi)();

聲明了一個返回整數的函數 f,一個返回一個整數指針的函數 fpi,以及一個返回一個整數的函數的指針 pfi。 在這些中都沒有>指定的參數類型; 他們是老式的。

在新式聲明中

int strcpy(char *dest, const char *source), rand(void);

strcpy 是一個返回 int 的函數,有兩個參數,第一個是字符指針,第二個是指向常量字符的指針

資料來源:- K&R 書

希望能解開你的疑惑。。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM