为什么我们不直接使用Object文件?

时间:2015-01-19 01:52:53

标签: c++ c++11 gcc linker

^ 这不是重复,我在这里调查为什么一种方法是首选,而不是差异,仔细阅读会使同行评审员明白它并不重复。

我正在读那个:

GCC Visibility

突然之间,这个问题突然出现在我的脑海里。有没有理由使静态或共享库比普通的Obj文件更受欢迎?

在创建静态/共享库时,我们会丢失很多信息并且还有优化的机会(对我来说并不重要)。相反,我们可以将源文件编译成Obj文件,然后将所有Obj文件(也是库的Obj文件)链接到最终的可执行文件中。

=> 我们保留所有信息,特别适用于异常处理和防止重复的type_info(使用C ++的依赖注入依赖于C ++ 11 Type_info,但是不能保证你不会得到重复的std::type_info对象对于不同图书馆的不同课程。)

如果能见度给你,你可以做一个" Ghost Compile / Link"步骤(编译应用程序,然后链接到静态库,看看我们是否得到"未定义的符号"因为访问内部的东西)然后继续使用真正的编译/链接(一切都是Obj文件,然后是Obj到可执行文件) )

最终的可执行文件将更小更快,不存在异常问题,构建系统将更加友好Emscripten:)。

您是否看到了可能出现的问题(我已经使用过并且工作完美无缺,但可能已经存在的代码可能会出现重复问题?对于大型代码库来说可能不可行?)

小例:

我的静态库是从2个文件编译的:

MyFoo.hpp

//declare MyFoo to be internal to my library
void __attribute__ ((visibility ("hidden"))) MyFoo();

MyFoo.cpp

#include <MyFoo.hpp>
#include <iostream>
void MyFoo(){
    std::cout<<"MyFoo()"<<std::endl;
}

MyBar.cpp

#include <MyBar.hpp>
#include <MyFoo.hpp>
#include <iostream>
void MyBar(){
    std::cout<<"MyBar() calling "; MyFoo(); //calling MyFoo
}

当我们编译时,我们得到2个ObjFiles

MyFoo.o
MyBar.o

当我们链接时,我们得到

MyLib.a

MyBar,仍然可以&#34;看到&#34;并调用MyFoo(否则无法编译)。

当我创建可执行文件时如果我链接到MyLib.a,我只能调用MyBar这是正确的

#include <MyBar.hpp>
#include <MyFoo.hpp>
int main(){
    MyBar(); //ok
    //MyFoo(); // error undefined symbol
    return 0;
}

那是因为我丢失了一些信息(在大多数情况下都需要:注意我必须指定hidden),但因此可见&#34;可见性&#34;功能变成一个问题,因为隐藏的东西我们最终可能会有具有相同完整限定名称的不同类(在不同的库内)

在抛出异常或尝试使用std::type_info

时,这是一个问题

所以唯一可行的解​​决方案似乎是进行2步编译,1只是检查我们是不是打破了可见性(因此也就是API合同),第2次构建是为了避免上述链接中的问题(对调试异常很奇怪)行为或神秘的崩溃)。

链接MyFoo.oMyBar.omain.o togheter在概念上是错误的,因为允许编译以下代码

#include <MyBar.hpp>
#include <MyFoo.hpp>
int main(){
    MyBar(); //ok
    MyFoo(); // compile NOT OK!
    return 0;
}

但链接目标文件togheter是避免异常问题的唯一方法。

2 个答案:

答案 0 :(得分:2)

隐藏C ++中的东西

您可能想要了解更多关于如何在C ++中使用隐藏私有定义的程序员。其中一个主要选择是Pimpl Idiom:

Why should the "PIMPL" idiom be used?

这使得Foo.hpp完全包含私有,甚至直接包含在Bar.cpp实现中的定义(取决于它将会有多大。)

# project organization
include/Bar.hpp
src/Bar.cpp
src/Foo.hpp
src/Foo.cpp

include下的文件将与其他项目共享。 src下的任何内容都是100%私有的(例如,如果你查看Qt源代码,你会看到&lt; name&gt; Private.h文件是pimpl。)

Bar.hpp中,您无法引用Foo,如有必要,您也可以使用其指针:

class Foo;

class Bar
{ 
   ...
   std::shared_ptr<Foo> f_foo;
   ...
};

然后在.cpp中包含.hpp以获得实际定义:

#include "Bar.hpp"
#include "Foo.hpp"
...
// Bar implementation
...

你也可以在Bar.cpp中实现Foo,假设它不是太大。这样你也可以使用无名称命名空间(即隐藏声明而不使用g ++技巧):

// in Bar.cpp

namespace
{

Class Foo
{
    ...implementation of Foo...
};

} // no name namespace

Class Bar
{
    ...implementation of Bar, can reference Foo...
};

规避信息丢失

现在,您的主要观点是信息丢失,因为现在您不知道Foo是什么。您的评论之一:我无法dynamic_cast<Foo *>(ptr)。真?如果Foo预计是私有的,那么为什么你的用户能够动态投射呢?!?如果你认为你可以给我任何一个理由,那就知道你是完全错的。

此外,Foo可能会抛出一个私有异常:

class Foo
{
   ...
   void func() { throw FooException("ouch!"); }
   ...
};

你有两个解决这个问题的主要方法。

当你实现Foo时,你知道它本身就是私有的,所以最简单的方法是永远不要引发一个私有异常(一般来说这没什么意义,但你可能想要这样...... )

如果您必须拥有私有异常,则必须转换异常。所以在Bar中,你从Foo中捕获所有私有异常并将它们转换为公共异常,然后抛出它。坦率地说,这是一项更多的工作,一方面,但最重要的是,你要扔两次:慢!

class Foo
{
   ...
   // Solution 1, use a public exception from the start
   void func() { throw BarException("ouch!"); }
   ...
};

// Solution 2, convert the exception
class Bar
{
    void func()
    {
        try
        {
           f_foo.func();
        }
        catch(FooException const& e)
        {
            throw BarException(e.what());
        }
    }
};

有时你没有选择,只能抓住异常并转换它们,因为子类不能直接抛出你的公共异常(也许它是第三方库。)但坦率地说,如果你使用公共例外可以。

扩展主题

由于您感兴趣的是您在库之间丢失了大量信息,因此您可能会对Ada语言而不是C ++感兴趣。在Ada中,目标文件实际上包含了进一步编译其他包和可执行文件所需的所有信息,而不仅仅是进一步的链接。由于Ruby on Rail是Ada和Eiffel的产物,你肯定会在语言对象文件中出现相同的效果,虽然我不熟悉它,所以我无法分辨(尽管我不知道如何如果没有这样的话,它们会让它发挥作用!)

否则,其他人已经解释了C / C ++目标文件和库。我没有太多可以添加他们自己的评论。请注意,这些存在于60年代(可能是早期的70年代),尽管格式发展得相当多,但文件仍然非常局限于text(即编译+汇编代码)块和过去一样。

答案 1 :(得分:1)

库是方便的包装。

  • 大量代码通常会包含许多目标文件。管理与每个单独目标文件的链接可能会给库的最终用户带来不必要的混淆和压倒性。

  • 对象文件可能会更改版本之间的名称,即使接口相同,并且与最终用户没有区别。将这些更改保留在库抽象中会使最终用户的生活变得更简单。

  • 与工具箱一样,在相应的位置使相关功能更容易。