C++域作用符在函數聲明和定義中的意義?

如下代碼在是vs2015:

void f() {}

class a1
{
public:
void f() {}
void g()
{
void f(); //void ::f();
f();
}
}

這樣的話,很自然調用的是全局的f()。然而使用了注釋里的聲明後,不會報錯,但是調用的是成員函數f。

為何在函數聲明中使用:: 不會出錯,但是應該是無效的聲明(畢竟之後的調用沒有受到其影響)。請問語法分析對函數聲明中的域作用符是如何解析的

另外我發現在如下代碼中:

int f() {return 0;}
int ::f() {return 1;}
int ::f() {return 2;}

int main()
{
return f();
}

vs2015不會報錯,甚至將f()的定義認同為return 2的那個,也就是最後一次出現的f()定義。這樣的話就有些詭異難以理解,請高手予以解釋。

當然,這些代碼在gcc中全部會出錯,指明了域作用符這樣用是不對的,應該是符合預期的。關鍵是想問一下關於這一點有什麼特殊之處,導致兩個編譯器對此有明顯的不同?標準中是怎麼解釋的?

另外有興趣的話也請大家麻煩看下這個問題,http://www.zhihu.com/question/45766253,希望能從語法分析的層面解釋一下錯誤的原因。


先說第一部分。

函數聲明中使用::在語法上沒問題,這裡的聲明有效,引用的是全局作用域中的f。

以void ::f();為例,自頂向下的語法展開路徑大致是這樣的,這裡省略了部分為空的展開。

declaration-seq
-&> block-declaration
-&> simple-declaration
-&> decl-specifier-seq init-declarator-list ;
-&> decl-specifier init-declarator-list ;
-&> type-specifier init-declarator-list ;
-&> simple-type-specifier init-declarator-list ;
void -&> init-declarator-list ;
void -&> init-declarator ;
void -&> declarator ;
void -&> noptr-declarator parameters-and-qualifiers ;
void -&> declarator-id parameters-and-qualifiers ;
void -&> id-expression parameters-and-qualifiers ;
void -&> qualified-id parameters-and-qualifiers ;
void -&> nested-name-specifier unqualified-id parameters-and-qualifiers ;
void :: -&> unqualified-id parameters-and-qualifiers ;
void :: f -&> parameters-and-qualifiers ;
void :: f -&> (parameter-declaration-clause) ;
void :: f () ;

::是從nested-name-specifier展開得到的,屬於qualified-id的一部分。

void ::f();這種聲明表示在全局作用域中f這個名字指代一個非成員函數。調用f時g的作用域里並不存在f,所以向上一層,最先找到的是當前類成員函數,類中不存在f這個名字的時候才會去全局作用域找,如果想要直接調用全局的同樣要加上::操作符。

而void f();這種聲明則表示在當前作用域中f這個名字指代一個非成員函數。調用f時最先找到這個聲明,繼續找定義就會找到全局的版本,因為類中的f是成員函數。

關於第二部分,同一文件中重複定義違背了One-Definition Rule(ODR),標準中也沒有其它規定為全局作用域的函數作特殊處理,VC的作法不合標準。

其實如果不是全局而是具體指定了命名空間或是某個類的話,VC是可以檢測到重複定義的,也許是全局空間的處理邏輯沒考慮全,我覺得可以提個bug。


看了看第二個程序,跟了下彙編,發現前邊兩個f() vs2015都沒有反彙編出來,也肯定打不上斷點!

還是挺有意思的,但是實際上f() 跟 ::f()這樣聲明是沒有什麼區別的,實際上在別的編譯器下這樣聲明肯定是報錯的,肯定是重定義了!然後特意查了下c++ primer 作用域發現是這樣定義的,關於內層作用域跟外層作用域,內層作用域肯定優先於外層作用域的一個很簡單的例子

int a = 3;

int main()

{

int a = 4;

std::cout&<&< a&<&}

a輸出是4,這時候我懷疑是第一個作用域把第二個作用域包含進去了,導致只有第二個函數才有用。同理第二個把第三個包含進去了,但是這樣也不很怪,因為這樣函數輸出是不會很慢的,實際上我寫第三個這樣的程序的時候

#include&

int a() { return 32; }

int ::a() { return 33; }

int main()

{

std::cout&<&}

int ::a() { return 34; }

程序的響應是非常慢的而且輸出的是34。將近2s.現在比較懷疑的是編譯器把這三個全局函數也重載了。醬


Cpp primer 5e 7.4.1


1 函數中不允許定義函數

2 第二個默認應該是redefinition 應該是VC為了一些歷史代碼瞎搞出的兼容/bug


推薦閱讀:

在 64 位平台開發時是否應盡量避免使用指針?
C++中為什麼派生類中只有基本類型時,delete一個指向派生類的基類指針時卻沒有內存泄漏?
c++在執行運行時多態時,為什麼需要借用rtti來判斷對象真實類型?
一個關於C++模板的問題?
關於c++模板推導失敗,這是編譯器的bug嗎?

TAG:C | CC | VisualC | C標準 | C編程 |