GCC에서 라이브러리가 연결된 순서에 따라 오류가 발생하는 이유는 무엇입니까?


답변:


558

(보다 정교한 텍스트를 얻으려면이 답변의 기록을 참조하십시오.하지만 독자가 실제 명령 줄을 보는 것이 더 쉽다고 생각합니다).


아래의 모든 명령이 공유하는 공통 파일

$ cat a.cpp
extern int a;
int main() {
  return a;
}

$ cat b.cpp
extern int b;
int a = b;

$ cat d.cpp
int b;

정적 라이브러리에 연결

$ g++ -c b.cpp -o b.o
$ ar cr libb.a b.o
$ g++ -c d.cpp -o d.o
$ ar cr libd.a d.o

$ g++ -L. -ld -lb a.cpp # wrong order
$ g++ -L. -lb -ld a.cpp # wrong order
$ g++ a.cpp -L. -ld -lb # wrong order
$ g++ a.cpp -L. -lb -ld # right order

링커는 왼쪽에서 오른쪽으로 검색하고 확인할 수없는 기호를 메모합니다. 라이브러리가 심볼을 해석하면 해당 라이브러리의 오브젝트 파일을 사용하여 심볼을 해석합니다 (이 경우 libb.a에서 둘 다).

정적 라이브러리의 서로에 대한 종속성은 동일하게 작동합니다. 즉, 기호가 필요한 라이브러리가 먼저 있어야하고, 그런 다음 기호를 해석하는 라이브러리가되어야합니다.

정적 라이브러리가 다른 라이브러리에 의존하지만 다른 라이브러리가 다시 이전 라이브러리에 의존하는 경우주기가 있습니다. 당신은에 의해 주기적으로 종속 라이브러리를 묶어이 문제를 해결할 수 -(-)같은 -( -la -lb -)(당신은 같은 괄호 탈출해야 할 수도 -\(-\)). 그런 다음 링커는 동봉 된 lib를 여러 번 검색하여 순환 종속성이 해결되도록합니다. 또는 라이브러리를 여러 번 지정할 수 있으므로 각각이 서로 앞에 -la -lb -la있습니다.

동적 라이브러리에 연결

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -L. -ld -o libb.so # specifies its dependency!

$ g++ -L. -lb a.cpp # wrong order (works on some distributions)
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong order
$ g++ -Wl,--as-needed a.cpp -L. -lb # right order

여기에서도 동일합니다. 라이브러리는 프로그램의 오브젝트 파일을 따라야합니다. 정적 라이브러리와 비교했을 때의 차이점은 동적 라이브러리가 종속성을 스스로 정렬 하기 때문에 라이브러리의 종속성에 대해 신경 쓰지 않아도된다는 것 입니다.

최근의 일부 배포판은 기본적으로 --as-needed링커 플래그 를 사용 하여 프로그램의 객체 파일이 동적 라이브러리보다 우선합니다. 해당 플래그가 전달되면 링커는 실제로 실행 파일에 필요하지 않은 라이브러리에 링크하지 않습니다 (왼쪽에서 오른쪽으로 감지). 최근의 아치 리눅스 배포판은 기본적 으로이 플래그를 사용하지 않으므로 올바른 순서를 따르지 않아도 오류가 발생하지 않았습니다.

의 의존성 생략 올바르지 않습니다 b.so에 대한 d.so이전을 만들 때입니다. 링크 할 때 라이브러리를 지정해야 a하지만 a실제로 정수 b자체 는 필요하지 않으므로 b자체 종속성 을 신경 쓰지 않아야합니다 .

다음은 종속성을 지정하지 않은 경우의 의미에 대한 예입니다. libb.so

$ export LD_LIBRARY_PATH=. # not needed if libs go to /usr/lib etc
$ g++ -fpic -shared d.cpp -o libd.so
$ g++ -fpic -shared b.cpp -o libb.so # wrong (but links)

$ g++ -L. -lb a.cpp # wrong, as above
$ g++ -Wl,--as-needed -L. -lb a.cpp # wrong, as above
$ g++ a.cpp -L. -lb # wrong, missing libd.so
$ g++ a.cpp -L. -ld -lb # wrong order (works on some distributions)
$ g++ -Wl,--as-needed a.cpp -L. -ld -lb # wrong order (like static libs)
$ g++ -Wl,--as-needed a.cpp -L. -lb -ld # "right"

바이너리가 어떤 의존성을 가지고 있는지 살펴보면 바이너리 자체는 libd단지 libb뿐만 아니라에 의존 한다는 것을 알 수 있습니다. libb이 방법으로 나중에 다른 라이브러리에 의존하는 경우 바이너리를 다시 연결해야합니다 . 그리고 누군가 가 런타임에 플러그인을 동적으로 로드하는 것을 libb사용하여 dlopen로드하면 호출도 실패합니다. 따라서 "right"정말로 있어야합니다 wrong.


10
모든 기호가 해결 될 때까지 반복하십시오. 토폴로지 정렬을 관리 할 수 ​​있다고 생각할 것입니다. LLVM에는 자체적으로 78 개의 정적 라이브러리가 있으며 who-knows-what 종속성이 있습니다. 또한 컴파일 / 링크 옵션을 알아내는 스크립트도 있지만 모든 상황에서 사용할 수는 없습니다.
Steve314

6
@Steve는 프로그램 lorder+ 가하는 tsort일입니다. 그러나 순환 참조가있는 경우 때로는 순서가 없습니다. 그런 다음 모든 것이 해결 될 때까지 라이브러리 목록을 순환해야합니다.
Johannes Schaub-litb

10
@Johannes-최대로 강하게 연결된 구성 요소 (예 : Tarjans 알고리즘)를 판별 한 후 구성 요소의 (내재적으로 비순환) digraph를 토폴로지 적으로 정렬하십시오. 각 구성 요소는 하나의 라이브러리로 취급 될 수 있습니다. 구성 요소에서 하나의 라이브러리가 필요한 경우 종속성 주기로 인해 해당 구성 요소의 모든 라이브러리가 필요합니다. 따라서 실제로 모든 것을 해결하기 위해 모든 라이브러리를 순환 할 필요가없고 어색한 명령 줄 옵션이 필요하지 않습니다. 잘 알려진 두 알고리즘을 사용하는 한 가지 방법으로 모든 경우를 올바르게 처리 할 수 ​​있습니다.
Steve314

4
"--start 그룹의 아카이브 --end 그룹"을 "- - (아카이브)"또는 사용 :이 우수한 대답 한 중요한 세부 사항을 추가하고 싶습니다 순환 종속성을 해결하는 유일한 확실한 방법입니다 때마다 때문에, 링커는 아카이브를 방문하고 현재 해결되지 않은 기호를 확인하는 객체 파일 만 가져 와서 확인되지 않은 기호를 등록합니다 . 이로 인해 종속성 그래프에서 연결된 구성 요소를 반복하는 CMake의 알고리즘이 때때로 실패 할 수 있습니다. (자세한 내용은 링커에 대한 Ian Lance Taylor의 훌륭한 블로그 게시물 을 참조하십시오 .)
jorgen

3
귀하의 답변으로 연결 오류를 해결하는 데 도움이되었으며 문제가 발생하지 않도록하는 방법을 매우 명확하게 설명했지만 왜 이런 방식으로 작동하도록 설계되었는지는 알고 있습니까?
Anton Daneyko

102

GNU ld 링커는 소위 스마트 링커입니다. 이전 정적 라이브러리에서 사용되는 함수를 추적하여 조회 테이블에서 사용되지 않은 함수를 영구적으로 버립니다. 결과적으로 정적 라이브러리를 너무 일찍 링크하면 나중에 해당 라이브러리의 함수를 더 이상 링크 라인의 정적 라이브러리에서 사용할 수 없게됩니다.

일반적인 UNIX 링커는 왼쪽에서 오른쪽으로 작동하므로 모든 종속 라이브러리를 왼쪽에 놓고 해당 종속성을 만족하는 라이브러리를 링크 라인의 오른쪽에 두십시오. 일부 라이브러리는 다른 라이브러리에 의존하고 다른 라이브러리는 다른 라이브러리에 의존한다는 것을 알 수 있습니다. 여기가 복잡해집니다. 순환 참조와 관련하여 코드를 수정하십시오!


2
gnu ld / gcc 만있는 것입니까? 아니면 링커에서 일반적인 것입니까?
Mike

2
분명히 더 많은 유닉스 컴파일러에도 비슷한 문제가 있습니다. MSVC는 이러한 문제를 완전히 해결하지는 못했지만 그렇게 나쁘지는 않은 것으로 보입니다.
MSalters

4
모든 MS 도구 체인을 사용하는 경우 링커 순서를 올바르게 설정하고 문제를 인식하지 못하기 때문에 MS 개발 도구는 이러한 문제를 많이 나타내지 않습니다.
Michael Kohne

16
MSVC 링커는 모든 라이브러리에서 참조되지 않은 심볼을 검색하므로이 문제에 덜 민감합니다. 라이브러리 순서 는 둘 이상의 라이브러리에 심볼이있는 경우 어떤 심볼이 해결되는지에 영향 줄 수 있습니다 . MSDN에서 : "라이브러리는 명령 줄 순서로 검색되며 다음과 같은 경고가 있습니다. 라이브러리에서 오브젝트 파일을 가져올 때 해결되지 않은 기호는 먼저 해당 라이브러리에서 검색된 다음 명령 행에서 다음 라이브러리를 검색합니다. / DEFAULTLIB (기본 라이브러리 지정) 지시문 다음 명령 행의 시작 부분에있는 모든 라이브러리에 "
Michael Burr

4
"... 스마트 링커 ..." - "스마트 링커"가 아니라 "싱글 패스"링커로 분류됩니다.
jww

54

정적 라이브러리가 관련된 경우 GCC와 함께 작동하는 방식을 명확하게 보여주는 예는 다음과 같습니다 . 다음 시나리오가 있다고 가정 해 봅시다.

  • myprog.o-포함하는 main()기능libmysqlclient
  • libmysqlclient-예제를 위해 정적입니다 (물론 libmysqlclient거대한 공유 라이브러리를 선호합니다 ). 에서 /usr/local/lib; 그리고 물건에 의존libz
  • libz (동적)

이것을 어떻게 연결합니까? (참고 : gcc 4.3.4를 사용하여 Cygwin에서 컴파일 한 예)

gcc -L/usr/local/lib -lmysqlclient myprog.o
# undefined reference to `_mysql_init'
# myprog depends on libmysqlclient
# so myprog has to come earlier on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# we have to link with libz, too

gcc myprog.o -lz -L/usr/local/lib -lmysqlclient
# undefined reference to `_uncompress'
# libz is needed by libmysqlclient
# so it has to appear *after* it on the command line

gcc myprog.o -L/usr/local/lib -lmysqlclient -lz
# this works

31

-Wl,--start-group링커 플래그에 추가 하면 순서에 관계없이 순환 종속성이 있는지 상관하지 않습니다.

Qt에서 이것은 다음을 추가하는 것을 의미합니다.

QMAKE_LFLAGS += -Wl,--start-group

많은 시간을 낭비하고 링크 속도를 늦추지 않는 것 같습니다 (어쨌든 컴파일보다 시간이 덜 걸립니다).


8

또 다른 대안은 라이브러리 목록을 두 번 지정하는 것입니다.

gcc prog.o libA.a libB.a libA.a libB.a -o prog.x

이 작업을 수행하면 참조가 두 번째 블록에서 해결되므로 올바른 순서로 신경 쓸 필요가 없습니다.


5

-Xlinker 옵션을 사용할 수 있습니다.

g++ -o foobar  -Xlinker -start-group  -Xlinker libA.a -Xlinker libB.a -Xlinker libC.a  -Xlinker -end-group 

ALMOST는

g++ -o foobar  -Xlinker -start-group  -Xlinker libC.a -Xlinker libB.a -Xlinker libA.a  -Xlinker -end-group 

조심해!

  1. 그룹 내 순서는 중요합니다! 예를 들면 다음과 같습니다. 디버그 라이브러리에는 디버그 루틴이 있지만 디버그가 아닌 라이브러리의 버전은 동일합니다. 디버그 라이브러리 FIRST를 그룹에 배치해야합니다. 그렇지 않으면 디버그 버전이 아닌 버전으로 해결됩니다.
  2. 그룹 목록의 각 라이브러리 앞에 -Xlinker가 있어야합니다.

5

링커를 "gcc"또는 "g ++"로 호출하는 경우 "--start-group"및 "--end-group"을 사용하면 해당 옵션을 링커-오류를 표시하지 않습니다. 라이브러리 순서가 잘못되면 정의되지 않은 기호가있는 링크가 실패합니다.

GCC에 인수를 링커에 전달하도록 지시하려면 "-Wl,-start-group"등으로 작성해야합니다.


2

링크 순서는 적어도 일부 플랫폼에서 중요합니다. 라이브러리와 연결된 응용 프로그램이 잘못된 순서로 충돌하는 것을 보았습니다.


2

나는 이것을 많이 보았습니다. 우리 모듈 중 일부는 100 개의 라이브러리와 시스템 및 타사 라이브러리를 초과하여 연결됩니다.

다른 링커 HP / Intel / GCC / SUN / SGI / IBM / etc에 따라 일부 플랫폼에서 라이브러리를 두 번 나열해야하는 미해결 함수 / 변수 등을 얻을 수 있습니다.

대부분의 경우 라이브러리, 코어, 플랫폼, 다른 추상화 계층 구조 계층을 사용하지만 일부 시스템의 경우 여전히 link 명령에서 순서를 따라야합니다.

솔루션 문서에 부딪 치면 다음 개발자가 다시 해결할 필요가 없습니다.

예전 강사는 " 높은 응집력과 낮은 커플 링 " 이라고 말 했지만 오늘날에도 마찬가지입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.