2016-03-03 435 views
13

我有一個簡單的代碼,我的功能之前,主要的功能就像宣稱:功能函數聲明與定義Ç

int function1(); 
int function2(); 

int main() { 
    /* ... */ 
    function1(x,y); 
    function2(x,y); 
    /* .... */ 
} 

int function1(int x, float y) { /* ... */ } 
int function2(int x, float y) { /* ... */ } 

而我的主要功能後,我的定義:

有有些區別,當我在main之前聲明函數是這樣的嗎?

int function1(int x, float y); 
int function2(int x, float y); 

int main() { 
    /* ... */ 
    function1(x,y); 
    function2(x,y); 
    /* .... */ 
} 

int function1(int x, float y) { /* ... */ } 
int function2(int x, float y) { /* ... */ } 
+0

您的前兩行用不同於以後聲明的簽名(不帶參數)的函數聲明函數。所以前兩行是不需要的。函數聲明描述參數的名稱,數量和類型以及返回類型。兩個函數可以具有相同的名稱但參數不同。它們不能僅在返回類型上有所不同。 – BryanT

+9

@BryanT這是不正確的(儘管這在C++中是正確的)。在C語言中,函數聲明中的空括號表示可以使用_any_類型的_any_個參數。如果你明確想要零參數,使用'(void)';參見'main'的標準簽名之一:'int main(void){...}'。 – szczurcio

+0

我同意。我在想C++。但是,將它們完全按照它們的定義進行宣佈並不是更好嗎? – BryanT

回答

15

是的,他們是不同的。

在第一個示例中,您只是告訴編譯器關於函數的名稱和返回類型,而不是其預期的參數。

在第二個示例中,在調用函數之前,要告訴編譯器函數的完整簽名,包括返回類型和預期參數。

第二種形式幾乎普遍更好,因爲它可以幫助編譯器做更好的工作,當你調用函數時有錯誤的類型或數量的參數時會警告你。

還要注意int function()在C是可以接受任何參數,不接受沒有參數的函數的函數。爲此,您需要明確的void,即int function(void)。這主要是從C++趟到C

參見: Why does a function with no parameters (compared to the actual function definition) compile?

爲了證明爲什麼第一,陳舊的形式是現代的C不好,下面的程序編譯沒有與gcc -Wall -ansi -pedanticgcc -Wall -std=c11警告。

#include<stdio.h> 
int foo(); 

int main(int argc, char**argv) 
{ 
    printf("%d\n", foo(100)); 
    printf("%d\n", foo(100,"bar")); 
    printf("%d\n", foo(100,'a', NULL)); 
    return 0; 
} 

int foo(int x, int y) 
{ 
    return 10; 
} 

UPDATE:中號&中號提醒我注意,我的例子使用intfloat的職能。我認爲我們都可以同意宣稱int function1()是不好的形式,但是我聲明這個聲明接受任何論點是不正確的。有關相關規範的部分,請參閱弗拉德的解答,解釋爲什麼是這種情況。

+0

你還應該提到OP的第一種情況是未定義的行爲,即使編譯器沒有診斷它(它不需要這樣做) –

+0

當然,第二種情況更好,因爲第一種情況仍然存在未定義行爲,其中意味着在我的編譯器中,它無法使用默認設置進行編譯。 –

2

在第一種情況下,main()執行對每個參數整數促銷和float - 到 - double促銷。這些被稱爲「默認參數促銷」。所以你最終可能會錯誤地調用函數,通過傳遞intdouble函數期望intfloat

請參閱Default argument promotions in C function calls和答案的更多細節。

14

是的,它們是不同的;第二個是正確,第一個作爲整個是錯誤的。它是那麼錯誤,GCC 5.2.1拒絕完全編譯它。它適用於你的一切都只是一個fluke

/* this coupled with */ 
int function1(); 

int main() { 
    /* this */ 
    function1(x, y); 
} 

/* and this one leads to undefined behaviour */ 
int function1(int x, float y) { 
    /* ... */ 
} 

在上面的代碼中,聲明int function1();沒有指定的參數類型(它沒有原型),這被認爲是一個過時功能在C11(和C89,C99就此而言)標準。如果調用這種函數,則默認參數促銷將在參數上完成:int按原樣傳遞,但float被提升爲double

由於您的實際函數需要參數(int, float)而不是(int, double),這會導致未定義的行爲。即使你的函數預期爲(int, double)但是y是一個整數,或者說你用function1(0, 0);而不是function(0, 0.0);來調用它,你的程序仍然會有未定義的行爲。幸運的GCC 5.2.1聲明說的function1的聲明和定義是矛盾的:

% gcc test.c 
test.c:9:5: error: conflicting types for ‘function1’ 
int function1(int x, float y) { 
    ^
test.c:9:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration 
int function1(int x, float y) { 
^ 
test.c:1:5: note: previous declaration of ‘function1’ was here 
int function1(); 
    ^
test.c:12:5: error: conflicting types for ‘function2’ 
int function2(int x, float y) { 
    ^
test.c:12:1: note: an argument type that has a default promotion can’t 
    match an empty parameter name list declaration 
int function2(int x, float y) { 
^ 
test.c:2:5: note: previous declaration of ‘function2’ was here 
int function2(); 
    ^

和編譯器退出,錯誤代碼,而我tcc編譯它令人高興的是,沒有任何診斷,沒有什麼。它只是產生破損的代碼。當然,如果你在頭文件中有聲明,並且在不包含該聲明的不同編譯單元中定義,情況也是如此。


現在,如果編譯器檢測不到這種情況下,在運行時,什麼可能發生,如預期的未定義行爲

例如,假設參數在棧上傳遞的情況;在32位處理器上intfloat可能適合4個字節,而double可能是8個字節;然後函數調用將推動xintydouble即使是float - 總來電會推12個字節和被叫只能期待8

在另一種情況下,假設你會打電話來的函數與2個整數。調用代碼然後將這些加載到整數寄存器中,但調用者期望在浮點寄存器中加倍。浮點寄存器可以包含一個陷阱值,當它被訪問時會終止你的程序。

什麼是最壞的情況,你的程序可能現在表現預期,從而包含heisenbug當你重新編譯編譯器的端口是一個較新的版本,或到另一個平臺的代碼,可能會出現問題。

+0

除非沒有編譯器會向後傾斜以告知它,否則將警告你「錯誤」的形式。請參閱我的答案中的示例程序。 –

+0

@Antti Haapala第一個代碼片段沒有錯。 –

+3

@VladfromMoscow它有未定義的行爲,所以通過我的書它是該死的錯 –

14

不同之處在於,在第二個代碼片段中存在函數原型,然後編譯器檢查參數的數量和類型與參數的數量和類型相對應。如果編譯器發現不一致,編譯器可以在編譯時發出錯誤。

如果沒有函數原型如在第一代碼段,則編譯器執行對包括所述整數促銷和浮點類型的表達式轉化爲雙鍵入每個參數默認參數提升。如果在這些操作之後,升級參數的數量和類型與參數的數量和類型不一致,則行爲未定義。編譯器可能無法發出錯誤,因爲函數定義可能位於其他編譯單元中。

這裏是從C標準相關引號(6.5.2.2函數調用)

2如果將表示所調用的函數的表達具有類型 包括一個原型,參數的數目應同意參數數量爲 。每個參數的類型應該使其 值可以分配給具有其相應參數的 類型的非限定版本的對象。

6如果將表示所調用的函數的表達具有類型 不包括原型,整數提升上 每個參數執行,且具有float類型參數被晉升爲 兩倍。這些被稱爲默認參數促銷。如果參數個數不等於參數個數,則 行爲未定義。如果函數的定義類型爲 包含原型,並且原型以省略號 (,...)結尾,或者升級後的參數類型不是 與參數類型兼容,則行爲未定義爲 。如果函數定義的類型不包含 包含原型,並且升級後參數的類型與促銷後參數的類型不兼容,則 行爲不確定,但以下情況除外:

- 一種提升類型是有符號整數類型,另一種提升類型 是對應的無符號整數類型,值爲 可在兩種類型中表示;

- 這兩種類型都是指向 字符類型或void的合格或不合格版本的指針。

至於你的代碼片段,那麼如果第二個參數的類型爲double那麼代碼將是格式良好的。但是,由於第二個參數的類型爲float,但相應的參數將被提升爲double,那麼第一個代碼片段具有未定義的行爲。