【问题标题】:Namespace and static class members linking命名空间和静态类成员链接
【发布时间】:2013-03-10 02:10:07
【问题描述】:

假设我有两个文件:

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

最后是我的问题:

为什么这个示例代码编译没有任何错误,并且我们能够调用 class fooprivate 方法?

使用 gcc 4.6.3 编译,但不仅如此。 我知道编译器不区分这两个符号(func 函数来自命名空间 foo 和私有函数 foo 来自 class 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 的编译器不会链接这两个符号。

【问题讨论】:

    标签: c++ gcc linker g++


    【解决方案1】:

    为什么编译器不报错?

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

    为什么链接器不抱怨?

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

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

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

    如果你也想在namespace foo 中实现func()

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

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

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

    【讨论】:

    • 好的,我什么都懂,但问题是为什么 gcc 编译器会有这样的行为。 Windows 编译器能够区分这些符号。命名空间和类不一样,我赖特吗?编译器应该知道命名空间和类之间的所有区别,不是吗?
    • 编译器知道,但链接器不知道。但是,编译器会将每个文件转换为单独的目标文件。您定义这两个文件的方式是它们与编译器完全分离。 VS 做的很好,但不是必须做的。因此,不是由 gcc 完成的。顺便说一句,Clang 也可以毫无怨言地编译它。
    • 我知道编译器知道,但我的意思是编译器为这些函数生成完全相同的符号,即使它们完全不同。所以也许 gcc 应该和 VS 编译器一样?它可以避免一些潜在的错误。
    • "class"、"struct" 和 "namespace" 都定义了编译器所关心的命名空间。您将无法在同一个文件中定义 namespace foo { }class foo { },因此编译器无需对符号名称进行不同的修饰。
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 2012-05-27
      • 2018-04-16
      • 1970-01-01
      • 1970-01-01
      • 2012-06-16
      • 2015-03-14
      • 2011-12-09
      • 1970-01-01
      • 2023-04-09
      相关资源
      最近更新 更多