2013-03-20 67 views
11

可以說我有兩個文件:命名空間和靜態類成員聯

/** 
* class.cpp 
*/ 
#include <stdio.h> 
class foo 
{ 
private: 
     int func(); 
}; 

int foo::func(void) 
{ 
     printf("[%s:%d]: %s\n", __FILE__, __LINE__, __FUNCTION__); 
     return -1; 
} 

/** 
* main.cpp 
*/ 
#include <stdio.h> 
namespace foo 
{ 
     int func(void); 
} 
int main(void) 
{ 
     int ret = foo::func(); 
     printf("[%s:%d]: ret=%d\n", __FILE__, __LINE__, ret); 
     return 0; 
} 

編譯如下:

g++ -o a.out main.cpp class.cpp 

有一個從可執行文件的輸出:

[class.cpp:15]: func 
[main.cpp:14]: ret=-1 

最後我的問題:

爲什麼這個示例代碼編譯沒有任何錯誤,我們都能夠調用私有方法類Foo

編譯與gcc 4.6.3但不僅。 我知道,編譯器並不區分這兩個符號(從命名空間和私有函數類FooFUNC功能)。從納米輸出:

nm class.o 
00000000 T _ZN3foo4funcEv 
00000017 r _ZZN3foo4funcEvE12__FUNCTION__ 
     U printf 

nm main.o 
     U _ZN3foo4funcEv 
00000000 T main 
     U printf 

我想問一下這種行爲是否正確與否?恕我直言,這是不正確的行爲,它是不安全的(打破封裝)。

我想提一下,visual studio 2008的編譯器不會鏈接這兩個符號。

回答

2

因爲您已將foo()定義爲main.cpp中的命名空間的成員,所以它是編譯器如何處理它的。 class/struct/namespace public/private等之間的區別取決於編譯器知道函數的定義 - 在這裏你故意設置它來欺騙它。

鏈接器不知道這種區別,它只是解析符號名稱,而在編譯器中,函數名稱的裝飾風格是相同的。符號名稱的裝飾方式在C++中是未指定的,所以這是完全有效的行爲。

3

爲什麼編譯器不會抱怨?

請注意,「類」,「結構」和「命名空間」都只是在編譯器方面定義了一個名稱空間。所以編譯器會相應地裝飾符號。如果您在同一個文件中定義了類和名稱空間,它會發生抱怨,但在這裏並不是這樣。

爲什麼連接器沒有抱怨?

你所編寫的代碼的方式,葉比class foo定義的func()namespace foo弱定義的func()。基本上,namespace foo中定義的func()只是一個沒有實現的簽名。你可以看到,這是留給鏈接程序在運行時解析符號,因爲執行不main.cpp

nm main.o 
     U _ZN3foo4funcEv 
//Here^^^^ 

這種方式,因爲命名空間和類名碰巧相同(導致相同的符號對於foo::func),鏈接器在鏈接時解決該符號,找到具有相同符號的強定義,並與其鏈接。

如果要實現在namespace foofunc()還有:

/** 
* main.cpp 
*/ 
#include <stdio.h> 

namespace foo 
{ 
    int func(void) { 
     printf("NM_FOO [%s:%d]: %s\n", __FILE__, __LINE__, __FUNCTION__); 
     return -1; 
    }; 
} 
int main(void) 
{ 
    int ret = foo::func(); 
    printf("[%s:%d]: ret=%d\n", __FILE__, __LINE__, ret); 
    return 0; 
} 

你會看到鏈接器抱怨有:

duplicate symbol foo::func()  in: 
/var/folders/.../class.o 
/var/folders/.../main.o 
ld: 1 duplicate symbol for architecture x86_64 

如果你看看main.o這個你會看到:

0000000000000064 T __ZN3foo4funcEv 
0000000000000158 S __ZN3foo4funcEv.eh 
00000000000000e0 s __ZZN3foo4funcEvE12__FUNCTION__ 
0000000000000000 T _main 
0000000000000128 S _main.eh 
       U _printf 

and class.o:

0000000000000000 T __ZN3foo4funcEv 
00000000000000a0 S __ZN3foo4funcEv.eh 
0000000000000080 s __ZZN3foo4funcEvE12__FUNCTION__ 
       U _printf 

兩者都定義相同的函數符號同樣強,導致鏈接器錯誤。

請記住,鏈接器不知道名稱空間和類之間的區別。它解析了目標代碼中的符號。只有強烈的重新定義發生時,它纔會抱怨。在一個強大的定義中,一個或多個較弱的定義在鏈接器世界中完美無瑕。

+0

好的,我明白一切,但問題是爲什麼有這樣的gcc編譯器的行爲。 Windows編譯器能夠區分這些符號。命名空間和類是不一樣的,我願意嗎?編譯器應該知道命名空間和類之間的所有區別,不是嗎? – pako 2013-03-20 20:11:30

+0

編譯器知道,但鏈接器不知道。但是,編譯器會將每個文件轉換爲單獨的對象文件。您定義這兩個文件的方式是它們與編譯器完全分離。 VS做的很好,但不需要做。因此,不是由gcc完成的。鏗鏘也編譯這沒有抱怨的方式。 – meyumer 2013-03-20 20:15:54

+0

我知道編譯器知道,但我的意思是編譯器爲這些函數生成完全相同的符號,即使它們完全不同。所以也許gcc應該和VS編譯器一樣?它可以避免一些潛在的錯誤。 – pako 2013-03-20 20:23:34