2013-12-03 134 views
3

我很難理解爲什麼C++的行爲比C更加「放鬆」,當涉及到解釋和爲函數的參數創建類型。C抱怨通過char **值傳遞給char const * const * const函數,但C++不會

C是世界上最簡單的事情,它堅持你寫的東西,就是這樣,C++另一方面是以一種扭曲的方式運行,我無法真正理解。

例如流行argv傳遞給函數時,這是一個char* []變得char**,我真的不知道爲什麼,我的期望和「希望」是char * const *,但我得到這個行爲來代替。

您還可以閱讀this article in PDF談到有關C和C之間的這種差異++,文章還用這句話結束:

雖然C++確定功能簽名時忽略頂級CV-預選賽參數 聲明,它不會 完全忽略那些cv-qualifiers。

由於我在網上找不到這個問題(嵌入式系統編程 - 2000年2月,這個老問題是免費的),我想知道這個短語可能意味着什麼。

有人可以解釋爲什麼這種行爲是在C++中的方式?

編輯:

我的一個例子是

#include <stdio.h> 

void foo(int argc, const char *const *const argv) { 
    printf("%d %s\n", argc, argv[0]); 
} 

int main(int argc, char *argv[]) { 
    foo(argc, argv); 
    return (0); 
} 

,如果你編譯這個與gcc 4.8.1你得到預期的錯誤

gcc cv_1.c 
cv_1.c: In function ‘main’: 
cv_1.c:8:3: warning: passing argument 2 of ‘foo’ from incompatible pointer type [enabled by default] 
    foo(argc, argv); 
^
cv_1.c:3:6: note: expected ‘const char * const* const’ but argument is of type ‘char **’ 
void foo(int argc, const char *const *const argv) { 
    ^

這種輸出使得隱含的事實, argv被解釋爲char**

+7

你的argv的例子實際上是C++繼承C.你的PDF會談約*簽名的東西*,它們僅用於重載分辨率。與簡歷檢查沒有任何關係。 –

+0

@HansPassant對於我所關心的,如果這個機制存在於頂層,它仍然會拋棄我的'const',並且我想知道這個怪異行爲背後的原因是什麼。在這個例子中'argv'只是一個玩具。 – user2485710

+3

請用一個具體的例子來幽默我們。 –

回答

6

函數參數可以通過值或引用傳遞。在引用的情況下,沒有頂級限定符,所以我們可以忽略這種情況。

對於按值參數,頂級限定符隻影響副本,並且完全獨立於用於複製構建該參數的原始數據。如果頂級預選賽並沒有從簽名下降,以下兩種功能將是有效的和不同的重載:

void f(int  i); 
void f(int const i); 

現在的問題是,給f(1)調用其中兩個重載應選擇?這裏的問題是,參數是否爲常量不會影響它可以構造的參數,所以編譯器永遠無法解析哪個是正確的重載。解決方案很簡單:在簽名中,頂級限定符被刪除,兩者都是相同的功能。

+0

我不明白這一點,對我來說關鍵的一步是,一個命名變量可能會有一個cv-qualifier,從你所說的話看起來它只是爲了節省創建問題的臨時值,看起來像權衡在那兒;但是你可以參考C++標準中的某個特定段落嗎? – user2485710

+0

@ user2485710:不,問題不是臨時性的,而是重載解決方案。如果你想要,用上面的命名變量替換1。頂級限定符適用於該函數的參數。對象是const或不是const,它們的構造函數(或轉換函數)的參數集完全相同。頂級'const'與調用方沒有關係,編譯器不能使用它來區分一個調用與另一個調用。選擇一個函數,添加/刪除頂級限定符,考慮哪些調用對任一簽名都有效:相同。 –

4

這是一個猜測,但原因是具有限定符的函數參數只是參數的一個副本。試想一下:

void foo(int * const a, volatile int b) { … } 

什麼這些限定詞說的是,在函數定義的代碼不會修改a(因爲它是常數)和的b值可能在未知的方式對C++實現訪問。 (這很奇怪; volatile對象通常是硬件寄存器或者進程間共享的數據,但假設我們正在調試一個問題,所以我們暫時標記爲b volatile,以確保我們可以在調試器中訪問它。 )

C++實現必須履行上ab當它被編譯和執行定義foo代碼這些限定符,所以它不能忽視這些限定符。

但是,請將調用者視爲foofooa視爲const或b作爲易失性與調用方無關。無論它指定的參數是否被複制(例如,,寄存器或堆棧)傳遞給foo。它所做的只是傳遞價值觀。如果foo聲明沒有限定詞:

void foo(int *a, int b) { … } 

然後調用者的行爲將不會改變:無論哪種方式,它只是傳遞的參數值,並調用foo。因此,這兩個foo的聲明與調用者的視圖相同,因此它們具有相同的簽名。

+0

所以,簡而言之,你是說可觀察到的行爲沒有變化(因爲'foo'),這足以重新考慮這個問題的嚴重性,並且可能會說它的方式足夠好嗎? – user2485710

3
void foo(char const * const * const) {} 
void bar(char *x[]) { 
    foo(x); // warning in C, nothing in C++ 
} 

原因編譯該示例中爲C產生一個警告,但C++不產生任何診斷不是因爲C和C++被治療char *[]爲不同的類型,或者因爲它們丟棄或在不同的地方插入const ,但只是因爲C和C++以不同方式定義'兼容指針類型'; C++放寬了規則,因爲C嚴格的規則並不能防止真正的錯誤。

請考慮:如何處理char const * const * constchar **不合法的問題?由於不能進行任何修改,因此不可能引入任何錯誤,因此這種限制幾乎沒有價值。

但是,這並不是說插入const s不允許可能產生錯誤的代碼。例如:

void foo(char const **c) { *c = "hello"; } 

void bar(char **c) { 
    foo(c); 
    **c = 'J'; 
} 

上面的代碼,如果允許,會寫入一個字符串常量,這是非法的。

C++仔細定義不兼容的指針類型,例如,上面的是不允許的,而仍然由C,以允許更多的安全程序比C

一個優點C中的規則放寬的規則是,他們是非常簡單的。基本上:

要使兩個指針類型兼容,兩者應具有相同的限定,並且兩個指針都應該是指向兼容類型的指針。

對於任何限定詞Q,一個指針指向一個非Q限定的類型可以被轉換爲一個指向 類型的q合格版本;存儲在原始值和轉換後的指針 中的值應相等。

另一方面,C++的規則繼續了幾個段落,並使用複雜的定義來明確指定允許哪些指針轉換。兔子洞在C++ 11 4.4 [conv.qual]段開始4.


我想知道什麼這個詞組可能意味。

他最有可能指的是如果一個參數在定義函數時被聲明爲const,那麼編譯器將不允許函數定義對參數執行非常量操作。

void foo(int x); 
void bar(int x); 

void foo(int const x) { 
    ++x; // error, parameter is const 
} 

void bar(int x) { 
    ++x; // okay, parameter is modifiable. 
} 
+0

目前還不清楚爲什麼編譯器會將'argv'解釋爲'char **'(因爲它是'char * []',因此編譯器會將它編譯爲第一篇文章)在那裏我開始嘗試這個。 – user2485710

+0

@ user2485710 C中的'char * []'和'char **'參數有什麼區別嗎? – bames53

+0

其中'a'是'int a []',我把'a'看作'int * const a',所以'argv'的'char **'實際上並不是我期望看到的。 – user2485710

5

您鏈接的PDF文章包含了一些關於他們的待遇頂級CV-預選賽的C和C++之間的差異不正確的語句。這些差異要麼不存在,要麼與文章中隱含的內容不同。

實際上,當涉及到確定函數簽名和函數類型時,C和C++有效地忽略函數參數聲明中的頂級cv限定符。 C和C++語言標準(及其底層機制)的措詞在概念上可能不同,但最終結果在兩種語言中都是相同的。

確定函數類型時,C++確實會直接忽略參數上的頂級cv限定符,如8.3.5/5中所述:「生成參數類型列表後,任何頂級cv限定符修改參數類型是刪除當形成功能類型「。

C不依賴於直接忽略這樣的限定詞,而是依賴於兼容類型的特定於C的概念。它說只有在頂級cv-qualifiers參數上有所不同的功能類型是兼容,這對於所有的手段和目的來說意味着它們是相同的。 6.7.5.3/15中函數類型兼容性的定義如下:「在確定類型兼容性和複合類型時,聲明合格類型的每個參數都被視爲具有不合格的版本其聲明的類型「。

鏈接的PDF文章指出,在C聲明下列順序是非法

void foo(int i); 
void foo(const int i); 

在現實中它是。C完全合法的只需要在同一範圍內的同一實體的所有聲明使用兼容類型(6.7/4)。以上兩個聲明是兼容,這意味着它們只是在法律上重新聲明相同的功能。(在C++中的上面的聲明也是法律和他們也重新聲明相同的功能。)

有關其他實施例中,在C和C++以下初始化是有效

void foo(const int i); 
void bar(int i); 

void (*pfoo)(int) = foo;  // OK 
void (*pbar)(const int) = bar; // OK 

與此同時C和C++ 在確定函數參數的局部變量類型時,同樣考慮了頂級cv限定符。例如,在C和C++以下代碼是非法的構造

void foo(const int i) { 
    i = 5; // ERROR! 
} 

在C和C++,在其參數的一個頂層CV-資格聲明的函數可以用完全不同的CV在後面定義對其參數進行限定。頂級cv-qualification中的任何差異都不構成C++中的函數重載。


此外,您多次提到,char *[]被解釋爲char **爲相關的事情。我沒有看到相關性。在函數參數列表中,T []聲明總是等效於T *聲明。但是這與頂級cv-qualifiers完全沒有關係。

與此同時,編輯中的代碼示例因爲與頂級cv限定符無關的原因而無法編譯。它無法編譯,因爲在C語言中沒有從char **const char *const *的隱式轉換。請注意,此轉換不涉及任何頂級cv-qualifiers。影響此轉換的const限定符僅出現在間接的第一級和第二級上。

這確實涉及C和C++之間的區別。在C和C++中都不允許從char **const char **的隱式轉換(例如,請參閱here)。但是,C++允許從char **const char *const *的隱式轉換,而C仍然沒有。你可以閱讀更多關於它here。但是請注意,在所有這些情況下,頂級cv限定符都是完全不相關的。他們根本沒有任何作用。

+0

描述此標準的段落是什麼?模板也一樣嗎? – user2485710

+0

我敢打賭。至於文章,這是相當古老的。在2000年前的那些年裏,我身邊的人仍然很高興地使用了在DOS環境下工作的IIRC「Turbo C++ 32」編譯器,並且在涉及所有重要的部分時遇到了很多問題。也許本文的作者簡單地使用了類似的東西.. – quetzalcoatl

1

觀察結果太大而不能發表評論。

只有char const * const * const x中的第一個const會引發C警告。
C++(Visual)抱怨8中的2個。不知道爲什麼?

恕我直言:兩種語言在第三個const上都沒有區別,從調用函數的角度來看,它們顯得多餘。

void fooccc(char const * const * const x) { if(x) return; } 
void foocc_(char const * const *  x) { if(x) return; } 
void fooc_c(char const *  * const x) { if(x) return; } 
void fooc__(char const *  *  x) { if(x) return; } 
void foo_cc(char  * const * const x) { if(x) return; } 
void foo_c_(char  * const *  x) { if(x) return; } 
void foo__c(char  *  * const x) { if(x) return; } 
void foo___(char  *  *  x) { if(x) return; } 

int g(char *x[]) { 
    fooccc(x); // warning in C passing argument 1 of 'fooccc' from incompatible pointer type 
    foocc_(x); // warning in C " 
    fooc_c(x); // warning in C " error in C++ cannot convert parameter 1 from 'char *[]' to 'const char **const ' Conversion loses qualifiers 
    fooc__(x); // warning in C " error in C++ cannot convert parameter 1 from 'char *[]' to 'const char **'  Conversion loses qualifiers 
    foo_cc(x); // no problem in C no problem in C++ 
    foo_c_(x); // no problem in C no problem in C++ 
    foo__c(x); // no problem in C no problem in C++ 
    foo___(x); // no problem in C no problem in C++ 
    return 0; 
    } 

注:Eclipse中,GCC -std = C99 -O0 -g3 -Wall
C++的Visual Studio 10.0

+0

我相信C++規則歸結爲:在任何級別插入常量需要在所有更高級別插入const,除了頂級無關緊要。 – bames53

+0

@ bames53如果您可以隨時訪問另一個C++編譯器,請感謝您的發現和編譯器信息。 – chux

+0

在[isocpp.org](http://isocpp.org/get-started)網站上列出了幾個在線編譯器,但是不是廣泛地測試實現,我只是閱讀C++規範。你的發現在這裏與我的閱讀和一些快速測試(clang和vC++) – bames53