为什么链接器不保留封装?

时间:2016-06-20 23:43:23

标签: c++ linker header-files encapsulation

让我们假设以下标题为foo.h:

class Foo {
  private:
    void print() const;
};

并关注foo.cpp:

#include <iostream>

#include "foo.h"

void Foo::print() const {
    std::cout << "Secret" << std::endl;
}

另一个头文件foo1.h,它与foo.h相同,除非方法print被声明为public:

class Foo {
  public:
    void print() const;
};

这将是main.cpp,只需在foo1.h中调用print:

#include "foo1.h"

int main() {
    Foo f;
    f.print();
    return 0;
}

对我来说似乎很奇怪的是,以下链接将起作用:

g++ foo.cpp -c -o foo.o
g++ main.cpp -c -o main.o
g++ main.o foo.o -o exec
./exec

最后一个命令将输出:

Secret

因此,在不知道类Foo的具体实现的情况下,知道它的声明并拥有其目标文件,即使它们被声明为私有,我们也可以创建它的方法。

我的问题是:

  1. 为什么会这样? Linker不考虑私人和公开声明吗?

  2. 这种行为在实践中有用吗?如果是,它是如何使用的?我猜这可能对测试有用。

3 个答案:

答案 0 :(得分:4)

首先,因为你违反了“一个定义规则”(C ++ 11 3.2 / 5“一个定义规则”说不同翻译单元中的单独类定义必须“由相同的令牌序列组成”) ,就工具链而言,任何事情都会发生。它可以诊断错误,或者生成一个似乎有效的程序(如在测试中)。

您的实验产生结果的一个简单原因是编译器对类成员的访问是“强制执行”的,并且您告诉编译器成员Foo::print()的访问是公开的。

它符合工具链对由于其他原因(例如重载)执行的名称mangle中的成员的访问进行编码。但是,由于标准不要求工具链强制执行,因此实施者似乎认为他们不需要在链接时考虑访问控制。换句话说,我认为将访问控制编码到链接器使用的外部符号是可行的,但是没有完成这项工作;可能是因为严格来说没有必要。

请注意,Microsoft C ++确实包含对外部名称中成员的访问权限,因此您确实会收到链接时错误:

testmain.obj : error LNK2019: unresolved external symbol "public: void __thiscall Foo::print(void)const " (?print@Foo@@QBEXXZ) referenced in function _main testmain.exe : fatal error LNK1120: 1 unresolved externals

以下是g ++产生的符号(以及c++filt解码):

D:\so-test>nm test.o | grep Foo
000000000000008c t _GLOBAL__sub_I__ZNK3Foo5printEv
0000000000000000 T _ZNK3Foo5printEv

D:\so-test>nm testmain.o | grep Foo
                 U _ZNK3Foo5printEv

D:\so-test>c++filt _ZNK3Foo5printEv
Foo::print() const

以下是MS C ++生成的符号(以及解码):

D:\so-test>dumpbin /symbols test.obj | grep Foo
22D 00000000 SECTBA notype ()    External     | ?print@Foo@@ABEXXZ (private: void __thiscall Foo::print(void)const )

D:\so-test>dumpbin /symbols testmain.obj | grep Foo
009 00000000 UNDEF  notype ()    External     | ?print@Foo@@QBEXXZ (public: void __thiscall Foo::print(void)const )

答案 1 :(得分:3)

标准,[basic.def.odr]部分:

  
      
  1. 如果每个定义出现在不同的翻译单元中,并且定义满足以下要求,则程序中的类类型 [snip] 可以有多个定义。鉴于在多个翻译单元中定义了这样一个名为D的实体,那么

         

    - D的每个定义应由相同的令牌序列组成;以及

  2.   

您的程序违反了此规则,因为该类的两个定义不包含相同的标记序列。违反单一定义规则会使程序格式不正确。

  
      
  1. 为什么会这样?
  2.   

标准不明确,标准没有说明应该如何处理这种情况。工具链可以自由拒绝链接程序,但允许成功链接。您的链接器恰好执行后者。另一个链接器可能会做前者。

  

Linker不考虑私人和公开声明吗?

正如您在实验中观察到的那样,您的链接器似乎不考虑访问说明符。链接器不需要考虑它们。它们纯粹是编译时的概念。

  

这种行为在实践中有用吗?如果是,它是如何使用的?

依赖于此evil并且不可移植。也就是说,当源无法重新编译时,它有时可以在野外看作是解决API限制的肮脏黑客。

答案 2 :(得分:1)

链接器仅解析符号。每个C ++文件都是独立编译的,通过#include语句引入任何声明,构建符号(在.o文件中),然后链接器只对编译器输出起作用。 C ++访问修饰符(如private,protected和public)仅影响编译器,而不影响链接器。 (从技术上讲,链接器甚至不知道类,它只处理类成员的装饰/损坏符号名称。)尽管不建议,但可以修改类的声明以更改成员的访问权限,实际上“取消隐藏“他们。