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 编译,但不仅如此。我知道编译器不区分这两个符号(来自命名空间foo的func函数和来自类 foo的私有函数foo)。来自nm的输出:

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 中的编译器不会链接这两个符号。

4

2 回答 2

3

为什么编译器不抱怨?

请注意,就编译器而言,“类”、“结构”和“命名空间”都定义了一个命名空间。所以编译器相应地装饰符号。如果您在同一个文件中定义类和命名空间,它会抱怨,但这里不是这种情况。

为什么链接器不抱怨?

您编写代码的方式使func()定义的 innamespace foo弱于func()定义的 in class foo。基本上,func()定义的namespace foo只是一个没有实现的签名。您可以看到它留给链接器在运行时解析符号,因为实现不在main.cpp

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

这样,由于命名空间和类名恰好相同(导致 相同的符号foo::func),链接器在链接时解析符号,找到具有相同符号的强定义,并针对它进行链接。

如果你也实现func()namespace foo

/**
 * 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

和class.o:

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

两者都定义了同样强大的函数符号,导致链接器错误。

请记住,链接器不知道命名空间和类之间的区别。它解析目标代码中的符号。如果发生强烈的重新定义,它只会抱怨。一个或多个较弱的定义和一个强定义在链接器世界中是完全可以的。

于 2013-03-20T19:52:39.233 回答
2

因为您已将其定义foo()为 main.cpp 中命名空间的成员,所以编译器就是这样处理它的。类/结构/命名空间公共/私有等之间的区别取决于编译器知道函数的定义 - 在这里你故意开始愚弄它。

链接器不知道这些区别,它只是解析符号名称,并且在您的编译器的情况下,函数名称的装饰最终相同。符号名称的装饰方式在 C++ 中未指定,因此这是完全有效的行为。

于 2013-03-20T19:33:16.500 回答