【问题标题】:g++ Cygwin/Linux or version discrepancyg++ Cygwin/Linux 或版本差异
【发布时间】:2016-08-26 06:30:22
【问题描述】:

谁能解释一下两个 g++ 实例如何处理将以下代码编译到共享库的差异?

Foo.h

#ifndef Foo_h
#define Foo_h

void Foo();

#endif // Foo_h

Foo.cpp

#include "Foo.h"
#include <iostream>

void Foo()
{
    std::cout << "Greetings from Foo()!" << std::endl;
}

Bar.h

#ifndef Bar_h
#define Bar_h

void Bar();

#endif // Bar_h

Bar.cpp

#include "Bar.h"
#include "Foo.h"
#include <iostream>

void Bar()
{
    Foo();
    std::cout << "Greetings from Bar()!" << std::endl;
}

在真正的 Linux 机器上:

>g++ --version
g++ (GCC) 4.4.7 20120313 (Red Hat 4.4.7-11)
Copyright (C) 2010 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

>g++ -fpic -c Foo.cpp
>g++ -fpic -c Bar.cpp
>g++ -shared -o libFoo.so Foo.o
>g++ -shared -o libBar.so Bar.o
>

在 Cygwin 上:

>g++ --version
g++ (GCC) 5.4.0
Copyright (C) 2015 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
>g++ -fpic -c Foo.cpp
>g++ -fpic -c Bar.cpp
>g++ -shared -o libFoo.so Foo.o
>g++ -shared -o libBar.so Bar.o
Bar.o:Bar.cpp:(.text+0x9): undefined reference to `Foo()'
Bar.o:Bar.cpp:(.text+0x9): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `Foo()'
collect2: error: ld returned 1 exit status

我真的没有足够的 *nix-savvy 知道如何在任一盒子上安装不同/匹配版本的 g++ 以查看这是否是问题的原因(在其中一个盒子上我不会有无论如何,这样做的特权)。

我一直认为目标文件以及扩展的库(无论是静态的还是共享的)都允许具有未解析的符号,并且只有在链接可执行文件时才需要解析所有符号。这个概念在几年的开发经验中也基本成立,所以我对 Cygwin 上产生的错误感到困惑。我很想知道这里发生了什么。谢谢。

更新

下面的回答者提供了以下可行的建议:g++ -shared -o libBar.so Bar.o libFoo.so

查看 libBar.so 的结果内容:

>nm --demangle libBar.so | grep Foo
00000004e4b791c4 I __imp__Z3Foov
00000004e4b7903c I _head_libFoo_so
00000004e4b71750 T Foo()
00000004e4b793ec I libFoo_so_iname

根据我的理解,这意味着 Foo() 二进制包含在 libBar.so 中,即 Foo() 的编译二进制内容存在于 libBar.so 中。

这与我根据真正的 Linux 机器上的行为在脑海中的画面有点不同。我认为每个.so 都是“独立”的二进制代码,就像.o 文件或仅由一个目标文件组成的.a 文件一样。

我想我难以理解的是 Cygwin(或 g++ 5.4)的行为是说 library 不能有未解析的符号 - 这感觉与很多以前的经验在我心中根深蒂固。我知道可执行文件不能有未解决的问题,但库应该能够有未解决的问题,对吧?毕竟,你不能执行一个库——它没有main()。我确定 static 库可能有未解决的问题,我认为共享库和静态库之间的区别在于它们的代码是否在链接时 添加 到可执行二进制文件中,或者是否他们的代码由可执行文件在运行时查找。

感谢社区可以在这里进一步明确。谢谢。

【问题讨论】:

  • 在 Windows 上没有共享对象。有DLL。 Cugwin 尽最大努力隐藏差异,但它只能做这么多。默认情况下,DLL 不允许有未解析的符号。
  • 谢谢。您的解释(或在我看来)与@MartinBonner 的解释相矛盾。我将尝试更深入地了解这一切。同时,如果您非常确定自己的立场,您能否将其发布为答案以及您认为相关的任何进一步解释/背景?
  • @n.m. - 啊!这就解释了!你应该发布一个答案。

标签: c++ linux linker cygwin shared-libraries


【解决方案1】:

我认为这是 g++ 5.4 vs 4.4(顺便说一句,这是一个很大的不同),而不是 cygwin vs Linux。

共享对象与对象文件完全不同。在某些方面,它更像是一个可执行文件。 5.4 正在使用的模型是,当它链接共享对象时,它不需要拥有所有符号的副本,但 它需要告诉运行时加载器加载器哪些共享对象可以在中找到剩余的符号。我建议:

g++ -shared -o libBar.so Bar.o libFoo.so

(或者,您也可以使用-lFoo,但您需要正确设置库路径)。

更有趣的问题是为什么 g++ 4.4 可以这样工作:我不知道。

为什么nmlibBar.so 中显示Foo

Foo()包含在libBar.so 中的二进制文件。以下是非常方便的波浪形和近似值。如果您想要更准确,您将不得不阅读有关加载程序和可执行文件格式的信息。

libBar.so 的汇编器看起来像:

Bar():
    CALL 000000    # The zeros are a blank that will be filled in by
                   # the loader with the address of Foo
    PUSH "Greetings from Bar()!"
    PUSH 000000    # Blank to be filled with address of std::cout
    CALL 000000    # Blank to be filled with address of
                   # std::ostream::operator<<(const char*)
... etc

然后在libBar.so 的其他地方会有这样的部分:

Bar+1    Foo
Bar+9    std::cout
Bar+11   std::ostream::operator<<(const char *)

告诉加载器用Foo的地址填写Bar+1。最后会有一段说:

Foo                                   libFoo.so
std::cout                             libc.so
std::ostream::operator<<(const char*) libc.so

它告诉加载器它可以在libFoo.so 等中找到Foo。这是nm 报告的最后一部分。

【讨论】:

  • 谢谢,@MartinBonner。我将根据您的回答修改我的问题,因为它与相关问题相吻合。我想社区可以让我知道作为一个单独的问题是否更好。
  • 如果你的修改改变了很多事情以至于答案不同,那么它可能意味着它最好作为一个单独的问题。如果您只是提供有关此问题的更多信息,请继续。
【解决方案2】:

Windows 上没有共享对象。有DLL。 DLL 的行为与 Un*x 共享对象不同。特别是,它们不允许有未定义的符号。 Windows 中用于 DLL 和可执行文件的 PE(可移植可执行文件)格式无法表达它们。谷歌“pe dll”“未定义符号”,周围有很多信息。

Cygwin 非常努力地向程序员隐藏 Windows 的特性,但它只能做这么多。

当您将libBar.solibFoo.so 链接时,libFoo.so 中的代码实际包含在libBar.do 中。这将破坏在运行时加载 DLL 的目的。相反,链接过程会为从其他 DLL 导入的所有函数创建 stubs。这些不是真正的功能。您可以通过查找字符串来确保确实如此:

% strings libFoo.so | grep "Greetings from"
Greetings from Foo
% strings libBar.so | grep "Greetings from"
Greetings from Bar

【讨论】:

  • 谢谢你和@MartinBonner;综合你的两个答案,我想我现在对 Cygwin 如何尝试抽象出 Windows 细节有了更多的了解——但我想这些细节在这个级别的检查中已经很明显了。那么,我认为,如果我在一个真正的 *nix 机器上重复我的 g++ 5.4 实验,我会观察到预期的行为,即共享库不需要相互链接 并且 可以未解决的符号。我在运行nm 时看到的“已定义”符号一定是您所说的存根。再次感谢您。
猜你喜欢
  • 1970-01-01
  • 2015-08-16
  • 1970-01-01
  • 1970-01-01
  • 2012-07-04
  • 1970-01-01
  • 1970-01-01
  • 2013-10-07
  • 2015-12-13
相关资源
最近更新 更多