자주 쓰는 함수들을 라이브러리 형태로 만들어 두면, 프로그램을 짤 때 편리합니다. 오브젝트 파일로부터 라이브러리를 제작하고, 이를 헤더 파일과 함께 이용하는 방법을 알아볼텐데요. 만약 이러한 개념들이 낮설게 느껴지거나 C/C++ 프로그램을 빌드하는 과정을 더 자세히 알고 싶으시다면, 시작하기에 앞서서 다음 포스팅을 읽어보시면 큰 도움이 되리라 생각합니다.
예를 들어서 함수 Function이 mylib.c 라는 소스코드에 정의되어 있고, 프로토타입이 mylib.h 라는 헤더 파일에 다음과 같이 선언되어 있는 경우를 상정해 봅시다.
mylib.h
#ifndef MYLIB_H
#define MYLIB_H
double Function(double x);
#endif
mylib.c
double Function(double x) {
double ret;
// ... 함수의 본체 ...
return ret;
}
[1] 우선 mylib.c 를 컴파일해서 오브젝트 파일을 만듭니다.
[정적 라이브러리(static library)의 경우] gcc -c mylib.c
[동적 라이브러리(shared library)의 경우] gcc -fPIC -c mylib.c
[2] 오브젝트 파일로부터 라이브러리를 생성합니다.
* 볼드체로 표시된 mylib부분을 적당한 이름으로 바꿔줍니다.
[정적 라이브러리(static library)의 경우] ar rc libmylib.a mylib.o
[동적 라이브러리(shared library)의 경우] gcc -shared -o libmylib.so mylib.o
만약 여러 개의 소스 파일로부터 라이브러리를 만드는 경우라면, 라이브러리 파일 이름 뒤에 오브젝트 파일들의 이름을 나열해 줍니다. 예를 들어서 mylib1.c, mylib2.c, mylib3.c 소스 파일들로부터 각각 mylib1.o, mylib2.o, mylib3.o 오브젝트 파일들을 얻었다면, 다음과 같이 합시다.
[정적 라이브러리] ar rc libmylib.a mylib1.o mylib2.o mylib3.o
[동적 라이브러리] gcc -shared -o libmylib.so mylib1.o mylib2.o mylib3.o
이 때 헤더 파일은 소스 파일별로 하나씩 만들어도 되고, 모든 소스파일에 정의된 함수들의 프로토타입을 모아서 단일한 헤더 파일을 만들어도 무방합니다.
[3] 정적 라이브러리의 경우 libmylib.a 라이브러리 내의 인덱스를 만듭니다.
ranlib libmylib.a
이렇게 만들어진 라이브러리를 다른 프로그램에서 사용할 때는 mylib.h 를 포함시켜 주고, -lmylib 라는 옵션을 통해 링크시켜 주면 됩니다. 예를 들어서 다음과 같은 main.c 소스코드가 있다면,
main.c
// ...
#include<mylib.h>
/* ...
* 전처리기
* 전역변수
* 함수 프로토타입 등
* ... */
int main (int argc, char *argv[]) {
// ... 메인함수 내용 ...
double x, y;
// ... 메인함수 내용 ...
y = Function(x);
// ... 메인함수 내용 ...
return 0;
}
다음과 같이 빌드를 해줍니다.
- 컴파일
gcc main.c -c -I[mylib.h의 위치] - 링크
gcc main.o -L[libmylib.a(so)의 위치] -lmylib -o [실행파일 이름]
한 가지 주목할 점은 헤더 파일은 컴파일 과정에서, 그리고 라이브러리 파일은 링크 과정에서 개입한다는 것인데요. 소스 파일로부터 오브젝트 파일을 만드는 컴파일 과정에서는, 헤더 파일에 있는 프로토타입을 통해 함수의 존재를 알려주기만 하면 됩니다. 반면에 실행파일을 만드는 링크 과정에서는 함수의 구체적인 역할을 알려줘야 하는데, 이 때 라이브러리 파일이 들어가게 되는 거죠.
예를 들어서 mylib.h 와 libmylib.a(so) 가 /homes/INCLUDE 와 /homes/LIBRARY 에 각각 저장되어 있다면,
gcc main.c -c -I/homes/INCLUDE
gcc main.o -L/homes/LIBRARY -lmylib -o run.exec
라고 해줍니다. 그러면 run.exec 라는 이름의 실행파일이 생성되어 프로그램을 구동할 수 있게 됩니다.
터미널 콘솔을 열때 미리 경로를 지정해주면, -I 및 -L 옵션을 생략하는 것이 가능합니다.
- bash shell 의 경우
홈디렉토리에 있는 .bashrc 파일 (또는 macOS의 경우 .bash_profile 파일)을 열어서
export C_INCLUDE_PATH=/homes/INCLUDE:$C_INCLUDE_PATH
export LIBRARY_PATH=/homes/LIBRARY:$LIBRARY_PATH
를 추가합니다. - TC shell 의 경우
홈디렉토리에 있는 .tcshrc 파일을 열어서
setenv C_INCLUDE_PATH /homes/INCLUDE:$C_INCLUDE_PATH
setenv LIBRARY_PATH /homes/LIBRARY:$LIBRARY_PATH
를 추가합니다.
이렇게 하면, 컴파일러는 /homes/INCLUDE 에 있는 헤더파일과 /homes/LIBRARY 에 있는 라이브러리들을 자동으로 탐색하므로, -I 및 -L 옵션이 필요없죠. 환경변수를 설정하는데 있어서 :$C_INCLUDE_PATH 및 :$LIBRARY_PATH 를 추가하는 이유는 기존의 값들을 유지한 채로 새로운 디렉토리를 추가하기 위함입니다. 콜론 (:) 기호로 분리된 여러 개의 디렉토리를 추가할 수 있는 기능을 이용한 방법입니다.
동적 라이브러리를 이용하기 위해서는 LD_LIBRARY_PATH 라는 환경변수를 추가로 지정해줘야 하는데요. 이는 프로그램 실행파일에게 동적 라이브러리 파일의 위치를 알려주는 역할을 합니다. 정적 라이브러리의 경우 실행 파일에 관련정보가 저장되므로 프로그램을 빌드하고 나면 라이브러리 파일이 없어도 실행이 가능한 반면, 동적 라이브러리는 프로그램이 실행되는 시점에 라이브러리의 정보가 필요합니다. 컴파일과 링크에 필요한 LIBRARY_PATH 환경변수와 비슷하게 다음과 같이 설정할 수 있습니다.
- bash shell 의 경우
export LD_LIBRARY_PATH=/homes/LIBRARY:$LD_LIBRARY_PATH - TC shell 의 경우
setenv LD_LIBRARY_PATH /homes/LIBRARY:$LD_LIBRARY_PATH
출처 : https://randu.org/tutorials/c/libraries.php
* 정적 라이브러리와 동적 라이브러리의 차이점
* LIBRARY_PATH 와 LD_LIBRARY_PATH 의 차이점
여기서는 C언어로 라이브러리를 만드는 법에 대해 다루었습니다만, extern "C" 코드블록 및 __cplusplus 식별자를 사용하면 C++ 에서도 사용가능한 범용 헤더파일을 만들 수 있습니다. 본문의 예시에 나온 mylib.h 헤더파일의 경우 다음과 같이 바꿀 수 있습니다.
#ifndef MYLIB_H
#define MYLIB_H
#undef __WRAP_CXX_INI
#undef __WRAP_CXX_FIN
#ifdef __cplusplus
#define __WRAP_CXX_INI extern "C" {
#define __WRAP_CXX_FIN }
#else
#define __WRAP_CXX_INI /* empty */
#define __WRAP_CXX_FIN /* empty */
#endif
__WRAP_CXX_INI
double Function(double x);
__WRAP_CXX_FIN
#endif
이렇게 하면 C언어뿐만 아니라 C++ 프로그램에서도 라이브러리를 사용할 수 있게 되는데요. 이 방법에 대한 자세한 사항은 다음 포스팅을 참고하면 좋습니다.
앞에서 헤더 파일과 라이브러리 파일을 자동으로 탐색하는 위치를 설정하기 위해 환경변수들을 사용하는 것에 대해 언급 했었습니다. 여기서는 C/C++ 컴파일러가 사용하는 환경변수를 중심으로 이야기했습니다만, 일반적으로 프로그램은 여러 환경변수의 값들을 조회하고 이용하죠. 환경변수의 개념과 C/C++ 프로그램에서 이를 사용하는 방법에 대한 더 자세한 사항은 다음 포스팅에 소개되어 있습니다.
마이크로소프트의 통합 개발환경인 비주얼 스튜디오에서도 C언어 및 C++ 라이브러리를 직접 제작하고 사용할 수 있습니다. 자세한 내용이 궁금하시다면, 다음 포스팅이 큰 도움이 되리라 생각합니다.
같이 알고 있으면 좋은 C/C++ 팁들
명령행 인자
함수를 인자로 사용하기
포트란과의 하이브리드
동적 메모리 할당 (malloc)