g++ Cygwin/Linux 或版本差异

g++ Cygwin/Linux or version discrepancy

有人可以解释 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 来知道如何在任何一个盒子上安装 different/matching 版本的 g++ 看看这是否是问题的原因(在其中一个盒子上我会无论如何,我有幸这样做。

我一直认为目标文件以及扩展库(无论是静态的还是共享的)都允许具有未解析的符号,并且只有在 link 运行一个可执行文件时所有符号都需要解析得到解决。在多年的开发经验中,这个概念也几乎成立,所以我对 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 都是 "standalone" 二进制代码,就像一个 .o 文件,或者一个仅由一个目标文件组成的 .a 文件。

我想我无法理解的是 Cygwin(或 g++ 5.4)的行为是说 library 不能有未解析的符号 - 这感觉相反许多先前的经验在我心中根深蒂固。我知道可执行文件不能有未解决的问题,但是库应该能够有未解决的问题,对吗?毕竟,您无法 执行 库 - 它没有 main()。我确定 static 库可以有未解决的问题,我认为共享库和静态库之间的区别在于它们的代码是否 link-time added 到可执行二进制文件,或者它们的代码是否由可执行文件在运行时查找。

感谢社区可以在这里表达的进一步清晰。谢谢。

我认为这比 cygwin 与 Linux.

更多的是 g++ 5.4 与 4.4(顺便说一句,这是一个很大的区别)

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

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 报告的最后一部分。

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

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

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

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