為什麼gcc允許將參數傳遞給定義為無參數的函數?


Answers

出於傳統原因,使用()為參數列表聲明函數本質上意味著“在函數被調用時計算出參數”。 要指定一個函數沒有參數,請使用(void)

編輯:我覺得我在這個問題上因老了而聲名大噪。 只是讓你的孩子知道編程曾經是什麼樣子,這是我的第一個程序 。 (不是C;它會告訴你我們在此之前必須採取的措施。)

Question

我不明白為什麼這個代碼編譯?

#include <stdio.h>
void foo() {
    printf("Hello\n");
}

int main() {
    const char *str = "bar";
    foo(str);
    return 0;
}

gcc甚至不會拋出一個警告,我將太多的參數傳遞給foo()。 這是預期的行為?




C99標準(6.7.5.3)和C11標準(6.7.6.3)規定:

標識符列表僅聲明該函數的參數的標識符。 作為該函數定義的一部分的函數聲明器中的空列表指定該函數沒有參數。 函數聲明器中的空列表不是該函數定義的一部分,它指定不提供有關參數數量或類型的信息。

由於foo的聲明是定義的一部分,聲明指定foo需要0個參數,所以調用foo(str)至少在道德上是錯誤的。 但是如下所述,C中存在不同程度的“錯誤”,編譯器在處理某些“錯誤”時可能會有所不同。

舉一個簡單的例子,考慮下面的程序:

int f() { return 9; }
int main() {
  return f(1);
}

如果我用Clang編譯上面的代碼:

tmp$ cc tmp3.c
tmp3.c:4:13: warning: too many arguments in call to 'f'
  return f(1);
         ~  ^
1 warning generated.

如果我使用gcc 4.8進行編譯,即使使用-Wall,也不會收到任何錯誤或警告。 以前的答案建議使用-Wstrict-prototypes,它正確地報告f的定義不是原型形式,但這實際上不是重點。 C標准允許以非原型形式的函數定義,比如上面的標準,並且標準清楚地表明該定義指定該函數接受0個參數。

現在有一個約束 (C11 Sec。6.5.2.2):

如果表示被調用函數的表達式的類型包含原型,則參數的數量應與參數的數量一致。

但是,這種約束不適用於這種情況,因為函數的類型不包含原型。 但是這是語義部分中的後續語句(不是“約束”),它適用於:

如果表示被調用函數的表達式的類型不包含原型...如果參數的數量不等於參數的數量,則行為是未定義的。

因此,函數調用確實會導致未定義的行為(即程序不是“嚴格符合”)。 但是,該標準僅需要一個實現來在違反實際約束時報告診斷消息,並且在這種情況下不存在違反約束的情況。 因此,gcc不需要報告錯誤或警告以成為“符合實施”。

所以我認為這個問題的答案,為什麼gcc允許它呢?是gcc不需要報告任何東西,因為這不是違反約束條件。 此外,gcc不會聲稱報告任何種類未定義的行為,即使使用-Wall或-Wpedantic。 這是未定義的行為,這意味著實現可以選擇如何處理它,並且gcc已經選擇了在沒有警告的情況下編譯它(顯然它只是忽略了參數)。