Cygwin/Linux或版本差异

g++ Cygwin/Linux or version discrepancy

本文关键字:版本 Linux Cygwin      更新时间:2023-10-16

谁能解释一下两个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知识来知道如何在两个机器上安装不同/匹配的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)的行为说不能有未解析的符号——这感觉与我以前根深蒂固的经验相反。我知道可执行文件不能有未解析文件,但库应该可以有未解析文件,对吧?毕竟,你不能执行一个库——它没有main()。我确信静态库可能有未解析的,我认为共享库和静态库之间的区别在于它们的代码是链接时添加到可执行二进制文件中的,还是它们的代码是可执行文件在运行时查找的。

感谢社区在这里提供的进一步澄清。谢谢你。

我认为这比cygwin vs Linux更像是g++ 5.4 vs 4.4(顺便说一下,这是一个很大的区别)。

共享对象与对象文件是完全不同的。在某些方面,它更像是一个可执行文件。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报告的最后一节。

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

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

当你链接libBar.solibFoo.so时,来自libFoo.so的代码是而不是物理上包含在libBar.do中的。这将破坏在运行时加载dll的目的。相反,链接过程为从其他dll导入的所有函数创建存根。这些不是真正的函数。您可以通过查找字符串

来确保情况确实如此:
% strings libFoo.so | grep "Greetings from"
Greetings from Foo
% strings libBar.so | grep "Greetings from"
Greetings from Bar