정적으로 링크 된 라이브러리 간의 기호 충돌을 처리하는 방법은 무엇입니까?
라이브러리를 작성할 때 가장 중요한 규칙과 모범 사례 중 하나는 라이브러리의 모든 심볼을 라이브러리 별 네임 스페이스에 넣는 것입니다. C ++는 namespace
키워드 덕분에 이것을 쉽게 만듭니다 . C에서 일반적인 접근 방식은 식별자 앞에 라이브러리 특정 접두사를 붙이는 것입니다.
C 표준의 규칙 (안전 컴파일)들에 대한 몇 가지 제약 넣어 : AC 컴파일러는 식별자의 처음 8 자만 볼 수 있으므로 foobar2k_eggs
및 foobar2k_spam
유효 같은 식별자로 해석 될 수있다 - 그러나 모든 현대 컴파일러는 임의의 긴 식별자 가능 , 그래서 우리 시대 (21 세기)에 우리는 이것에 대해 신경 쓸 필요가 없습니다.
그러나 심볼 이름 / ID를 변경할 수없는 일부 라이브러리에 직면하면 어떻게됩니까? 정적 바이너리와 헤더 만 얻었거나 원하지 않거나 스스로 조정하고 재 컴파일 할 수 없습니다.
적어도 정적 라이브러리 의 경우 매우 편리하게 해결할 수 있습니다.
라이브러리 foo 및 bar의 헤더를 고려하십시오 . 이 튜토리얼을 위해 소스 파일도 제공합니다.
예 /ex01/foo.h
int spam(void);
double eggs(void);
examples / ex01 / foo.c (불투명하거나 사용할 수 없음)
int the_spams;
double the_eggs;
int spam()
{
return the_spams++;
}
double eggs()
{
return the_eggs--;
}
예 /ex01/bar.h
int spam(int new_spams);
double eggs(double new_eggs);
examples / ex01 / bar.c (불투명하거나 사용할 수 없음)
int the_spams;
double the_eggs;
int spam(int new_spams)
{
int old_spams = the_spams;
the_spams = new_spams;
return old_spams;
}
double eggs(double new_eggs)
{
double old_eggs = the_eggs;
the_eggs = new_eggs;
return old_eggs;
}
우리는 그것들을 프로그램 foobar에서 사용하고 싶습니다.
예 /ex01/foobar.c
#include <stdio.h>
#include "foo.h"
#include "bar.h"
int main()
{
const int new_bar_spam = 3;
const double new_bar_eggs = 5.0f;
printf("foo: spam = %d, eggs = %f\n", spam(), eggs() );
printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n",
spam(new_bar_spam), new_bar_spam,
eggs(new_bar_eggs), new_bar_eggs );
return 0;
}
한 가지 문제가 즉시 드러납니다. C는 과부하를 알지 못합니다. 따라서 이름은 같지만 서명이 다른 두 개의 함수가 두 번 있습니다. 그래서 우리는 그것들을 구별 할 방법이 필요합니다. 어쨌든 컴파일러가 이것에 대해 무엇을 말해야하는지 봅시다 :
example/ex01/ $ make
cc -c -o foobar.o foobar.c
In file included from foobar.c:4:
bar.h:1: error: conflicting types for ‘spam’
foo.h:1: note: previous declaration of ‘spam’ was here
bar.h:2: error: conflicting types for ‘eggs’
foo.h:2: note: previous declaration of ‘eggs’ was here
foobar.c: In function ‘main’:
foobar.c:11: error: too few arguments to function ‘spam’
foobar.c:11: error: too few arguments to function ‘eggs’
make: *** [foobar.o] Error 1
좋아, 이것은 놀라운 일이 아니었다. 우리가 이미 알고 있거나 적어도 의심했던 것을 우리에게 말해 주었다.
그렇다면 원본 라이브러리의 소스 코드 나 헤더를 수정하지 않고 어떻게 든 식별자 충돌을 해결할 수 있을까요? 사실 우리는 할 수 있습니다.
먼저 컴파일 시간 문제를 해결할 수 있습니다. 이를 위해 헤더에는 #define
라이브러리에서 내 보낸 모든 기호의 접두사를 지정 하는 여러 전 처리기 지시문이 포함됩니다 . 나중에 우리는 멋지고 아늑한 wrapper-header로 이것을 수행하지만, 무슨 일이 일어나고 있는지 보여주기 위해 foobar.c 소스 파일 에서 그대로 수행했습니다 .
예 /ex02/foobar.c
#include <stdio.h>
#define spam foo_spam
#define eggs foo_eggs
# include "foo.h"
#undef spam
#undef eggs
#define spam bar_spam
#define eggs bar_eggs
# include "bar.h"
#undef spam
#undef eggs
int main()
{
const int new_bar_spam = 3;
const double new_bar_eggs = 5.0f;
printf("foo: spam = %d, eggs = %f\n", foo_spam(), foo_eggs() );
printf("bar: old spam = %d, new spam = %d ; old eggs = %f, new eggs = %f\n",
bar_spam(new_bar_spam), new_bar_spam,
bar_eggs(new_bar_eggs), new_bar_eggs );
return 0;
}
이제 컴파일하면 ...
example/ex02/ $ make
cc -c -o foobar.o foobar.c
cc foobar.o foo.o bar.o -o foobar
bar.o: In function `spam':
bar.c:(.text+0x0): multiple definition of `spam'
foo.o:foo.c:(.text+0x0): first defined here
bar.o: In function `eggs':
bar.c:(.text+0x1e): multiple definition of `eggs'
foo.o:foo.c:(.text+0x19): first defined here
foobar.o: In function `main':
foobar.c:(.text+0x1e): undefined reference to `foo_eggs'
foobar.c:(.text+0x28): undefined reference to `foo_spam'
foobar.c:(.text+0x4d): undefined reference to `bar_eggs'
foobar.c:(.text+0x5c): undefined reference to `bar_spam'
collect2: ld returned 1 exit status
make: *** [foobar] Error 1
... 먼저 상황이 악화 된 것 같습니다. 그러나 자세히 살펴보십시오. 실제로 컴파일 단계는 잘 진행되었습니다. 이제 충돌하는 심볼이 있다고 불평하는 링커 일 뿐이며 이것이 발생하는 위치 (소스 파일 및 라인)를 알려줍니다. 그리고 우리가 볼 수 있듯이 그 기호는 접두사가 없습니다.
nm 유틸리티 를 사용하여 기호 테이블을 살펴 보겠습니다 .
example/ex02/ $ nm foo.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams
example/ex02/ $ nm bar.o
0000000000000019 T eggs
0000000000000000 T spam
0000000000000008 C the_eggs
0000000000000004 C the_spams
So now we're challenged with the exercise to prefix those symbols in some opaque binary. Yes, I know in the course of this example we have the sources and could change this there. But for now, just assume you have only those .o files, or a .a (which actually is just a bunch of .o).
objcopy to the rescue
There is one tool particularily interesting for us: objcopy
objcopy works on temporary files, so we can use it as if it were operating in-place. There is one option/operation called --prefix-symbols and you have 3 guesses what it does.
So let's throw this fella onto our stubborn libraries:
example/ex03/ $ objcopy --prefix-symbols=foo_ foo.o
example/ex03/ $ objcopy --prefix-symbols=bar_ bar.o
nm shows us that this seemed to work:
example/ex03/ $ nm foo.o
0000000000000019 T foo_eggs
0000000000000000 T foo_spam
0000000000000008 C foo_the_eggs
0000000000000004 C foo_the_spams
example/ex03/ $ nm bar.o
000000000000001e T bar_eggs
0000000000000000 T bar_spam
0000000000000008 C bar_the_eggs
0000000000000004 C bar_the_spams
Lets try linking this whole thing:
example/ex03/ $ make
cc foobar.o foo.o bar.o -o foobar
And indeed, it worked:
example/ex03/ $ ./foobar
foo: spam = 0, eggs = 0.000000
bar: old spam = 0, new spam = 3 ; old eggs = 0.000000, new eggs = 5.000000
Now I leave it as an exercise to the reader to implement a tool/script that automatically extracts the symbols of a library using nm, writes a wrapper header file of the structure
/* wrapper header wrapper_foo.h for foo.h */
#define spam foo_spam
#define eggs foo_eggs
/* ... */
#include <foo.h>
#undef spam
#undef eggs
/* ... */
and applies the symbol prefix to the static library's object files using objcopy.
What about shared libraries?
In principle the same could be done with shared libraries. However shared libraries, the name tells it, are shared among multiple programs, so messing with a shared library in this way is not such a good idea.
You will not get around writing a trampoline wrapper. Even worse you cannot link against the shared library on the object file level, but are forced to do dynamic loading. But this deserves its very own article.
Stay tuned, and happy coding.
Rules of the C standard put some constraints on those (for safe compilation): A C compiler may look at only the first 8 characters of an identifier, so foobar2k_eggs and foobar2k_spam may be interpreted as the same identifiers validly – however every modern compiler allows for arbitrary long identifiers, so in our times (the 21st century) we should not have to bother about this.
This is not just an extension of modern compilers; the current C standard also requires the compiler to support reasonably long external names. I forget the exact length but it's something like 31 characters now if I remember right.
But what if you're facing some libraries of which you cannot change the symbol names / idenfiers? Maybe you got only a static binary and the headers or don't want to, or are not allowed to adjust and recompile yourself.
Then you're stuck. Complain to the author of the library. I once encountered such a bug where users of my application were unable to build it on Debian due to Debian's libSDL
linking libsoundfile
, which (at least at the time) polluted the global namespace horribly with variables like dsp
(I kid you not!). I complained to Debian, and they fixed their packages and sent the fix upstream, where I assume it was applied, since I never heard of the problem again.
I really think this is the best approach, because it solves the problem for everyone. Any local hack you do will leave the problem in the library for the next unfortunate user to encounter and fight with again.
If you really do need a quick fix, and you have source, you could add a bunch of -Dfoo=crappylib_foo -Dbar=crappylib_bar
etc. to the makefile to fix it. If not, use the objcopy
solution you found.
If you're using GCC, the --allow-multiple-definition linker switch is a handy debugging tool. This hogties the linker into using the first definition (and not whining about it). More about it here.
This has helped me during development when I have the source to a vendor-supplied library available and need to trace into a library function for some reason or other. The switch allows you to compile and link in a local copy of a source file and still link to the unmodified static vendor library. Don't forget to yank the switch back out of the make symbols once the voyage of discovery is complete. Shipping release code with intentional name space collisions is prone to pitfalls including unintentional name space collisions.
'Program Tip' 카테고리의 다른 글
C #에서 system.net.webrequest를 사용하여 JSON 응답을 얻는 방법은 무엇입니까? (0) | 2020.10.14 |
---|---|
JavaScript 함수의 이름을 지정하고 즉시 실행할 수 있습니까? (0) | 2020.10.14 |
char의 기본값은 무엇입니까? (0) | 2020.10.14 |
AngularJS : ng-model이 확인란에 대해 ng-checked에 바인딩되지 않음 (0) | 2020.10.14 |
Python 다중 처리를 시도하는 Windows의 RuntimeError (0) | 2020.10.14 |