gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 02:41:34 25 4
gpt4 key购买 nike

这个问题在这里已经有了答案:





link with static library vs individual object files

(1 个回答)


7年前关闭。




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

我正在阅读:

GCC Visibility

突然,这个问题在我脑海中浮现。是否有任何理智的理由使静态或共享库比普通的 Obj 文件更受欢迎?

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

=>
我们保留所有信息,对于异常处理和防止重复的 type_info 尤其有用(C++ 的依赖注入(inject)依赖于 C++11 Type_info,但不能保证您不会为不同库中的不同类获得重复的 std::type_info 对象)。

如果可见性让您担心,您可以执行“Ghost 编译/链接”步骤(编译应用程序,然后与静态库链接以查看我们是否因为访问内部内容而获得“ undefined symbol ”),然后继续进行真正的编译/链接(一切都到 Obj 文件,然后 Obj 到可执行文件)

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

您是否看到可能的问题(我已经在使用它并且工作完美,但可能已经存在的代码可能存在“重复”问题?对于大型代码库可能不可行?)

小例子:

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

我的Foo.hpp

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

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

我的酒吧.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,仍然可以“看到”并调用 MyFoo(否则无法编译)。

如果我链接到 MyLib.a 创建可执行文件时我只能调用 MyBar这是正确的
#include <MyBar.hpp>
#include <MyFoo.hpp>
int main(){
MyBar(); //ok
//MyFoo(); // error undefined symbol
return 0;
}

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

这是抛出异常或尝试使用 std::type_info 时的问题。

因此,唯一可行的解​​决方案似乎是进行 2 步编译,1 只是为了检查我们没有破坏可见性(以及 API 契约(Contract)),第二次构建是为了避免上述链接中的问题(奇怪的是调试异常行为或神秘崩溃)。

链接 MyFoo.o , MyBar.o , main.o togheter 在概念上是错误的,因为允许编译以下代码
#include <MyBar.hpp>
#include <MyFoo.hpp>
int main(){
MyBar(); //ok
MyFoo(); // compile NOT OK!
return 0;
}

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

最佳答案

在 C++ 中隐藏东西

作为一名 C++ 程序员,您可能想进一步了解如何隐藏私有(private)定义。主要选项之一是 Pimpl Idiom:

Why should the "PIMPL" idiom be used?

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

# project organization
include/Bar.hpp
src/Bar.cpp
src/Foo.hpp
src/Foo.cpp
include 下的文件将与其他项目共享。 src 下的任何内容是 100% 私有(private)的(例如,如果您查看 Qt 源代码,您会看到 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
...

你也可以实现 Foo在 Bar.cpp 里面假设它不是太大。这样你也可以使用无名命名空间(即隐藏没有 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 预计是私有(private)的,那么为什么您的用户应该能够动态转换到它?!?如果你认为你可以给我任何一个理由,那就知道你完全错了。

Foo 也可以抛出一个私有(private)异常:
class Foo
{
...
void func() { throw FooException("ouch!"); }
...
};

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

当您实现 Foo 时,您知道它本身就是私有(private)的,因此最简单的方法是永远不要引发私有(private)异常(这通常没有多大意义,但您可能想要这样......)

如果您必须有私有(private)异常,则必须对异常进行转换。因此,在 Bar 中,您从 Foo 捕获所有私有(private)异常并将它们转换为公共(public)异常,然后将其抛出。坦率地说,这需要更多的工作,一方面,但最重要的是你要扔两次:慢!
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());
}
}
};

有时您别无选择,只能捕获异常并转换它们,因为子类不能直接抛出您的公共(public)异常(可能是第 3 方库)。但坦率地说,如果可以的话,请使用公共(public)异常。

主题扩展

由于您看起来对在库之间丢失大量信息这一事实感兴趣,因此您可能对 Ada 语言而不是 C++ 感兴趣。在 Ada 中,目标文件实际上包含了进一步编译其他包和可执行文件所需的所有信息,而不仅仅是进一步的链接。由于 Ruby on Rail 是 Ada 和 Eiffel 的衍生品,因此您肯定会在该语言对象文件中出现相同的效果,尽管我不熟悉它,所以我无法确定(尽管我不知道它们是如何做到的没有这样的工作!)

否则,其他人已经解释了 C/C++ 目标文件和库。我没有太多要添加到他们自己的评论中。请注意,这些文件自 60 年代(可能是 70 年代初)以来就存在,尽管格式发生了很大变化,但文件仍然非常受限于 text。 (即编译+汇编代码) block 和过去一样。

关于c++ - 为什么我们不直接使用 Object 文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28016905/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com