링크(link) 시 심볼(symbol), 함수이름 충돌(conflict) 방지하기

C 프로그램에서 완전히 동일한 이름을 가지는 함수가 둘 이상 존재할 때는 어떻게 동작하게 될지 궁금하지 않나요 ?

특정 파일 하나를 컴파일하여 object 를 만들 때는 해당 파일 내에 충돌되는 이름이 없으면 되겠지만, 만약 같은 이름의 함수를 가진 여러 개의 파일이 각각 object 파일로 문제없이 컴파일된 후 링크를 하게 되면 문제가 있을지 없을지...
문제가 없다면 왜 없는 것인지... 문제가 있다면 없게 하는 방법은 없는지...???

이런 궁금증이 저는 예전에 참 많이 생겼었습니다.
지금부터 그 원리들을 한번 공부해 보겠습니다.
이번 공부를 하기 전에 되도록이면 "프로그래밍 Tip" 쪽에 제가 올렸던 "C 와 C++ 프로그램 링크방법"이라는 강의를 먼저 쭈욱 보시기 바랍니다.

아래 코드의 컴파일, 링크 과정을 보면 최종 링크 단계에서 에러가 발생하였습니다.

sh> cat a.c

#include <stdio.h>
void func()
{
    printf("func in a.cn");
}

sh> cat b.c

#include <stdio.h>
void func()
{
    printf("func in b.cn");
}

sh> cat main.c

void func();
int main()
{
    func();
    return 0;
}

sh> gcc -c a.c

sh> gcc -c b.c

sh> gcc -c main.c

sh> gcc -o main a.o b.o main.o

b.o: In function `func':
b.c:(.text+0x0): multiple definition of `func'
a.o:a.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status

"multiple definition" 이라는 에러가 발생하는 것을 보니, link 시에 a.o 와 b.o 에 같은 이름의 func 가 있는지를 눈치챘나봅니다. ^^

상식적으로 당연히 안되겠죠 ?
이 에러는 a.o 와 b.o 를 이용하여 동적 라이브러리를 만들 때도 동일하게 발생합니다.
동적 라이브러리를 만들 때 링커(linker)는 이러한 각 object 의 symbol 들을 사전에 검사하기 때문입니다.

sh> gcc -fPIC -c a.c
sh> gcc -fPIC -c b.c
sh> gcc -shared -o libfoo.so a.o b.o

b.o: In function `func':
b.c:(.text+0x0): multiple definition of `func'
a.o:a.c:(.text+0x0): first defined here
collect2: ld returned 1 exit status

하지만, 아래의 예를 한번 보시죠. ar 을 이용하여 정적 라이브러리를 만든 후 링크를 하면 링크에러가 발생하지 않습니다.
ar 명령은 충돌 검사를 수행하지 않기 때문이죠.

sh> gcc -c a.c
sh> gcc -c b.c
sh> gcc -c main.c
sh> ar cr libfoo.a a.o b.o             # a.o -> b.o 의 순서로 object 를 묶어 정적 library 만듬
sh> gcc -o main main.o libfoo.a        # 에러발생 안함.
sh> ./main
func in a.c                            # a.c 에 있는 func 호출

sh> rm libfoo.a 
sh> ar cr libfoo.a b.o a.o             # a.o 와 b.o 의 순서를 바꾸어서 library 만듬
sh> gcc -o main main.o libfoo.a
sh> ./main
func in b.c                            # b.c 에 있는 func 호출

위의 실행 예를 보면 중요한 사실을 하나 발견할 수 있습니다.

"ar 을 이용하여 정적 라이브러리를 생성할 때 주어진 object 인자의 순서에 따라 호출되는 중복함수의 우선순위도 달라진다."는 것입니다. 즉, a.o 를 b.o 보다 먼저 주면 a.o 내에 있는 func() 가 먼저 호출됩니다.

이번에는 a.o 와 b.o 를 각각 liba.a, libb.a 로 만들어서 링크하면 어떻게 될까요 ?
이 경우도 정확하게 바로 위의 결과와 동일합니다. 호출되는 순서도 마찬가지이구요.

sh> ar cr liba.a a.o
sh> ar cr libb.a b.o
sh> gcc -c main.c
sh> gcc -o main main.o liba.a libb.a
sh> ./main
func in a.c
sh> gcc -o main main.o libb.a liba.a
sh> ./main
func in b.c

그렇다면, 각 파일을 동적 라이브러리로 생성해서 사용하면 어떻게 될까요 ?
저 위쪽에서는 a.o 와 b.o 를 하나의 동적 라이브러리로 묶을 경우 링크 에러가 발생했었는데요.

sh> gcc -fPIC -shared -o a.so a.c
sh> gcc -fPIC -shared -o b.so b.c
sh> gcc -fPIC -shared -o main.so main.c
sh> gcc -o main ./main.so ./a.so ./b.so      # a.so 와 b.so 의 순서를 바꾸면 결과도 바뀜
sh> ./main
func in a.c

결과는 위의 정적 라이브러리 결과와 같습니다.
각각의 파일을 라이브러리로 만들기 때문에 당연히 충돌오류는 발생하지 않는 것이고, 동적 링크할 때는 기본적으로 먼저 발견한 라이브러리의 symbol 정의가 사용됩니다.

이제 오늘 살펴볼 내용은 모두 마쳤습니다.

다음에는 LD_PRELOAD 라는 것을 배울텐데 이는 위에서 우리가 배운 개념때문에 이용할 수 있는 기능입니다.
즉, 최초로 발견하는 심볼을 사용한다는 개념을 이용하는 것이죠.
굉장히 흥미로운 일을 해낼 수 있습니다. ^^

You may also like...

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x