【问题标题】:Why doesn't gcc report error for following duplicate symbols?为什么 gcc 不报告重复符号的错误?
【发布时间】:2014-06-27 02:19:19
【问题描述】:

我正在使用 Ubuntu Linux 来构建 Android 和 Linux 二进制文件。我有一个静态库,它已被两个共享库链接,并且静态库中有一个全局对象。

据我了解,全局对象在*.so中都会存在,并且会因为每个共享库中的函数符号访问不同的全局变量而引起问题。

(我在构建两个共享库时在命令行中引用了静态库。我一直在使用-Wl,--whole-archive-Wl,-z,defs开关。所以共享库包含静态库的符号。)

所以问题是:

  1. 链接可执行文件时,为什么 GCC/LD 不报告这种情况下的重复符号错误?
  2. 这是否意味着我们永远不会链接到应用程序共享库和可执行文件本身中的同一个静态库?

-----------------编辑 1------------------------

正如 R Soha 所说,我们不应该在静态库中拥有状态,或者应该提供共享库。

而且我认为有这个限制是不好的和可悲的:静态库不应该有状态或者应该作为共享库提供。

原因是: 1. 全局变量是常用的,例如在我的例子中,它是一个单例对象。 2. 静态库可能由第三方提供。并且可能用户还使用了另一个第三方提供的共享库,该共享库已经链接了静态库,例如 boost。

【问题讨论】:

  • “一个由两个共享库链接的静态库”是什么意思?您的意思是您将程序与两个共享库和一个静态库链接?或者当您构建共享库时,您在构建每个共享库的命令行中引用了一个静态库?在任何一种情况下,共享库都不太可能包含来自静态库本身的任何内容。
  • 我指的是后一种情况。在构建两个共享库时,我在命令行中引用了静态库。我使用了-Wl,--whole-archive-Wl,-z,defs 开关。所以共享库中包含了静态库的符号。

标签: c++ linux gcc compiler-construction linker


【解决方案1】:

共享库行为依赖于平台的经验证据

这里有一些简单的代码,演示了两个共享库,它们都定义了一个符号(函数)abc_123() 以及调用相同函数的其他函数,以及一个使用这些函数的测试程序。

lib1.h

#ifndef JLSS_ID_LIB1_H
#define JLSS_ID_LIB1_H

extern void abc_123(int i);
extern void def_345(int i);

#endif

lib2.h

#ifndef JLSS_ID_LIB2_H
#define JLSS_ID_LIB2_H

extern void abc_123(int i);
extern void ghi_678(int i);

#endif

lib1a.c

#include <stdio.h>
#include "lib1.h"

void abc_123(int i)
{
    printf("Library 1:%s:%d:%s() - %d\n", __FILE__, __LINE__, __func__, i);
    printf("Note this extra message\n");
}

lib1b.c

#include <stdio.h>
#include "lib1.h"
#include "lib2.h"

void def_345(int i)
{
    printf("Library 1:%s:%d-->>%s() - %d\n", __FILE__, __LINE__, __func__, i);
    ghi_678(i+10);
    printf("Library 1:%s:%d<<--%s() - %d\n", __FILE__, __LINE__, __func__, i);
}

lib2a.c

#include <stdio.h>
#include "lib2.h"

void abc_123(int i)
{
    printf("Library 2:%s:%d:%s() - %d\n", __FILE__, __LINE__, __func__, i);
    printf("This is a completely different message\n");
}

lib2b.c

#include <stdio.h>
#include "lib2.h"

void ghi_678(int i)
{
    printf("Library 2:%s:%d-->>%s() - %d\n", __FILE__, __LINE__, __func__, i);
    abc_123(i * 10);
    printf("Library 2:%s:%d<<--%s() - %d\n", __FILE__, __LINE__, __func__, i);
}

test1.c

#include <stdio.h>
#include "lib1.h"
#include "lib2.h"

int main(void)
{
    printf("Cross-library linking and calling\n");
    printf("Main calling abc_123(29)\n");
    abc_123(29);
    printf("Main calling def_345(45)\n");
    def_345(45);
    printf("Main calling ghi_678(57)\n");
    ghi_678(57);
    printf("Demonstration over\n");
    return 0;
}

制作文件

CC     = gcc #/usr/bin/gcc
WFLAG1 = -Wall
WFLAG2 = -Wextra
WFLAG3 = -Wmissing-prototypes
WFLAG4 = -Wstrict-prototypes
WFLAG5 = -Wold-style-definition
WFLAG6 = -Werror
WFLAGS = ${WFLAG1} ${WFLAG2} ${WFLAG3} ${WFLAG4} ${WFLAG5} ${WFLAG6}
SFLAGS = -std=c11
GFLAGS = -g
OFLAGS = -O3
UFLAGS = # Set on command line
IFLAG1 = # -I${HOME}/inc
IFLAGS = # ${IFLAG1}

SOEXT   = dylib
LDFLAG1 = -L.
LDLIB1  = -lrary1
LDLIB2  = -lrary2
LDFLAGS = ${LDFLAG1}
LDLIBS  = ${LDLIB1} ${LDLIB2}

CFLAGS  = ${OFLAGS} ${GFLAGS} ${IFLAGS} ${SFLAGS} ${WFLAGS} ${UFLAGS}

LNKSHLIB = -shared

LIBRARY1 = library1.${SOEXT}
LIBRARY2 = library2.${SOEXT}

LIB1.o  = lib1a.o lib1b.o
LIB2.o  = lib2a.o lib2b.o
TEST1.o = test1.o

PROGRAM = test1

all: ${PROGRAM}

${PROGRAM}: ${TEST1.o} ${LIBRARY1} ${LIBRARY2}
    ${CC} ${CFLAGS} -o $@ ${TEST1.o} ${LDFLAGS} ${LDLIBS}

${LIBRARY1}: ${LIB1.o} ${LIBRARY2}
    ${CC} ${CFLAGS} ${LNKSHLIB} -o $@ ${LIB1.o} ${LDFLAGS} ${LDLIB2}

${LIBRARY2}: ${LIB2.o}
    ${CC} ${CFLAGS} ${LNKSHLIB} -o $@ ${LIB2.o}

在 Linux 上构建和运行

这是来自 Ubuntu 14.04 LTS VM(托管在 Mac OS X 10.9.3 上),编译器是 GCC 4.8.2。

$ make -B SOEXT=so UFLAGS=-fPIC
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -fPIC -c test1.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -fPIC -c lib1a.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -fPIC -c lib1b.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -fPIC -c lib2a.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -fPIC -c lib2b.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -fPIC -shared -o library2.so lib2a.o lib2b.o
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -fPIC -shared -o library1.so lib1a.o lib1b.o -L. -lrary2
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -fPIC -o test1 test1.o -L. -lrary1 -lrary2
$ LD_LIBRARY_PATH=$PWD:$LD_LIBRARY_PATH ./test1
Cross-library linking and calling
Main calling abc_123(29)
Library 1:lib1a.c:15:abc_123() - 29
Note this extra message
Main calling def_345(45)
Library 1:lib1b.c:17-->>def_345() - 45
Library 2:lib2b.c:15-->>ghi_678() - 55
Library 1:lib1a.c:15:abc_123() - 550
Note this extra message
Library 2:lib2b.c:17<<--ghi_678() - 55
Library 1:lib1b.c:19<<--def_345() - 45
Main calling ghi_678(57)
Library 2:lib2b.c:15-->>ghi_678() - 57
Library 1:lib1a.c:15:abc_123() - 570
Note this extra message
Library 2:lib2b.c:17<<--ghi_678() - 57
Demonstration over
$

请注意,每次都会调用来自library1.soabc_123(),即使调用函数是来自library2.soghi_678()

在 Mac OS X 上构建和运行

这是来自带有 GCC 4.9.0 的 Mac OS X 10.9.3。

$ make -B
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -c -o test1.o test1.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -c -o lib1a.o lib1a.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -c -o lib1b.o lib1b.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -c -o lib2a.o lib2a.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -c -o lib2b.o lib2b.c
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -shared -o library2.dylib lib2a.o lib2b.o
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -shared -o library1.dylib lib1a.o lib1b.o -L. -lrary2
gcc -O3 -g -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition -Werror -o test1 test1.o -L. -lrary1 -lrary2
$ ./test1
Cross-library linking and calling
Main calling abc_123(29)
Library 1:lib1a.c:15:abc_123() - 29
Note this extra message
Main calling def_345(45)
Library 1:lib1b.c:17-->>def_345() - 45
Library 2:lib2b.c:15-->>ghi_678() - 55
Library 2:lib2a.c:16:abc_123() - 550
This is a completely different message
Library 2:lib2b.c:17<<--ghi_678() - 55
Library 1:lib1b.c:19<<--def_345() - 45
Main calling ghi_678(57)
Library 2:lib2b.c:15-->>ghi_678() - 57
Library 2:lib2a.c:16:abc_123() - 570
This is a completely different message
Library 2:lib2b.c:17<<--ghi_678() - 57
Demonstration over
$

注意ghi_678() 中的代码从library2.so 调用abc_123(),而不是像Linux 上那样从library1.so 调用的版本。

您可以使用反向链接顺序,看看会发生什么。

道德

不要在多个共享库中构建包含相同功能的软件;如果您在平台之间移植,您会感到困惑。

【讨论】:

  • 很好的实验。更多分享。是的,实际上我支持 Linux 和 Android,当我将代码移动到静态库时,android 会中断,但 Linux 不会。我什至认为它可能通过一些宏来获得两个平台的不同行为。一个大教训。 :)
  • 为什么会有这种行为?
【解决方案2】:

你说:

据我了解,全局对象将同时存在于 *.so 中,并且会导致问题,因为每个共享库中的函数符号访问不同的全局变量。

没错。第一个 .so 使用的全局变量将不同于第二个 .so 使用的全局变量。

至于你的问题……

链接可执行文件时,为什么GCC/LD不报告这种情况下的重复符号错误?

就可执行文件而言,它只关心 .so 库的接口。它不关心它们是使用相同的静态库创建的这一事实。

这是否意味着我们永远不会链接到应用程序共享库和可执行文件本身中的同一个静态库?

静态库没有状态也没关系。如果它们有状态,您必须判断状态是需要在整个应用程序中是全局的,还是只在共享库中。

如果状态需要在整个应用程序中是全局的,最好创建另一个共享库来提供对全局状态的访问,而不是将其放在静态库中。

如果需要在每个共享库中维护状态,则将其放入静态库中即可。即便如此,创建一个提供数据访问权限的共享库将是一个更好的解决方案。

【讨论】:

  • @R Sahu,我还是不明白为什么 GCC 不报错。实际上可执行文件也引用了静态库。链接可执行文件时我不添加静态库,并将它的连接只是静默链接到 .so 库中的副本之一。所以静态库函数是.so库接口的一部分,gcc/linker应该报错吧?
  • @ZijingWu:同理library1.so可以定义'abc_123()`这样的符号,library2.so也可以定义abc_123()这样的符号,同样的道理没有冲突(假设链接顺序为-lrary1 -lrary2)。如果library2.so中的代码调用abc_123(),需要知道是调用library1.so还是library2.so中的版本。这是标准共享库的东西(实际上,标准库的东西;如果库都是静态的,那么所有东西都将使用abc_123() 的第一个版本)。
  • @JonathanLeffler,让我确认一下。如果library1.so 定义abc_123()library2.so 也定义abc_123(),如果我通过-lrary1 -lrary2 构建可执行文件,并且可执行文件调用abc_123()。你的意思是函数library1.so::abc_123()会被静默使用,甚至两个abc_123()都不一样?那么有没有关于这方面的标准文件?
  • 主要的可执行代码,来自library1.so的代码肯定会使用来自library1.soabc_123()。我不确定library2.so 中调用abc_123() 的代码是否最终会使用library1.so 的版本,或者是否最终会使用library2.so 的版本。 (我假设常规的外部可见符号;静态函数等在它们定义的库中是本地的,当然。)我没有关于这个主题的文档——尽管我确信它一定存在。
猜你喜欢
  • 1970-01-01
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-05-31
  • 2012-10-28
  • 2014-11-07
  • 2011-07-02
相关资源
最近更新 更多