라이브러리(library)란?
라이브러리(library)란 다른 프로그램에서 사용할 수 있는 코드의 모음을 의미합니다.
라이브러리를 만들기 위해서는 헤더 파일과 라이브러리를 구성하는 코드가 필요합니다. 헤더 파일은 사용자가 라이브러리를 사용하기 위해 호출할 함수와 변수의 프로토타입을 선언하며, 라이브러리 코드는 실제로 함수와 변수를 구현하는 코드입니다.
헤더 파일과 라이브러리 코드를 함께 컴파일하여 정적 라이브러리 또는 동적 라이브러리를 생성할 수 있습니다. 이때, 라이브러리 코드는 오브젝트 파일 형태로 제공되며, 라이브러리를 사용하는 프로그램은 이 오브젝트 파일을 링크하여 라이브러리를 사용할 수 있습니다.
헤더 파일은 라이브러리를 사용하는 사용자가 라이브러리를 쉽게 사용할 수 있도록 인터페이스를 제공합니다. 이를 통해 라이브러리를 사용하는 사용자는 라이브러리를 사용하는데 필요한 함수와 변수의 이름, 매개변수, 반환값 등을 알 수 있습니다. 또한, 헤더 파일은 라이브러리를 구현하는 소스 코드와 사용자 코드 간의 인터페이스를 명확히 하여 코드의 가독성과 유지보수성을 높이는 역할도 합니다.
따라서, 헤더 파일은 라이브러리를 사용하는 사용자가 라이브러리를 사용하기 위한 필수적인 정보를 제공하는 역할을 하기 때문에 따로 구분하여 사용합니다. 라이브러리를 만드는 개발자는 헤더 파일을 작성하여 라이브러리의 인터페이스를 제공하고, 라이브러리를 사용하는 사용자는 해당 헤더 파일을 include하여 라이브러리를 사용할 수 있습니다. 이를 통해 라이브러리를 쉽게 사용할 수 있으며, 라이브러리를 수정하더라도 헤더 파일만 수정하면 라이브러리를 사용하는 모든 코드에 영향을 주지 않습니다.
이렇게 만들어진 라이브러리는 여러 프로그램에서 재사용되어 코드의 중복을 막고 개발 시간을 단축시키는 등의 이점을 제공합니다.
라이브러리는 크게 static library와 dynamic library로 나눌 수 있습니다.
1. static library
Static 라이브러리는 컴파일 시에 라이브러리의 코드가 사용자의 프로그램에 복사되어 실행 파일에 포함됩니다.
이 때, 실행 파일은 라이브러리를 사용할 때마다 그 라이브러리의 모든 코드를 메모리에 로드합니다.
따라서, 여러 프로그램에서 같은 라이브러리를 사용하는 경우, 메모리를 많이 차지할 수 있습니다.
Static 라이브러리는 사용자가 컴파일러에게 직접 링크하도록 지정하는 방법으로 사용할 수 있습니다.
이 경우, 사용자의 소스 코드와 함께 실행 파일에 라이브러리 코드가 포함됩니다.
이러한 방식은 실행 파일의 크기가 크다는 단점이 있지만, 실행 파일이 라이브러리 파일에 의존하지 않으므로 이식성이 높습니다.
2. dynamic library
Dynamic 라이브러리는 실행 파일과 별도로 존재하며, 프로그램이 실행될 때 메모리에 로드됩니다.
따라서, 라이브러리의 코드는 여러 프로그램에서 공유될 수 있습니다.
이렇게 공유된 라이브러리는 메모리를 적게 사용할 수 있습니다.
Dynamic 라이브러리는 사용자가 라이브러리를 호출할 때마다 동적으로 로드됩니다.
이를 위해 프로그래밍 언어에 따라서는 로딩에 대한 명시적인 호출이 필요할 수 있습니다.
이 방식은 실행 파일의 크기를 줄일 수 있지만, 실행 파일이 라이브러리 파일에 의존한다는 단점이 있습니다.
또한, Dynamic 라이브러리는 라이브러리 코드가 변경될 경우, 실행 파일을 다시 빌드하지 않아도 되므로, 유지보수가 용이합니다. 그러나, 라이브러리 파일이 필요하므로 이식성이 낮을 수 있습니다.
shared object
UNIX 및 유닉스 계열 운영 체제에서 자주 등장하는 용어인 "shard object" 또한 동적 라이브러리(dynamic library)의 일종입니다
즉, Windows 운영 체제에서 사용되는 DLL(Dynamic Link Library)이 유닉스 운영체제에서는 SO(shared object)인 것입니다.
Shared objec 역시 실행 파일과 별도로 존재하는 라이브러리 파일이며, 런타임에 메모리에 로드되어 프로그램에서 사용됩니다.
따라서 여러 프로그램에서 공유될 수 있으므로 메모리를 절약할 수 있습니다. (동적 링크 통해 실행 파일 크기 줄일수 있음)
Shared object는 보통 .so (shared object) 확장자를 갖습니다. 사용자가 컴파일러에게 직접 링크하거나, 런타임에 로드하기 위해 명시적인 호출이 필요합니다.
Shared object는 다양한 용도로 사용될 수 있습니다.
예를 들어, 시스템 라이브러리와 같은 기본적인 함수를 포함하여, user-defined 함수, 플러그인, 응용 프로그램의 모듈 등을 구현 등에 사용될 수 있습니다. 또한, Shared object는 유지보수 및 업그레이드 작업을 용이하게 하므로, 대규모 프로젝트에서 매우 유용합니다.
링킹(linking)
링킹(Linking)은 프로그래밍에서 컴파일된 객체 파일(Object File)을 실행 파일(Executable File)로 결합하는 과정을 말합니다.
일반적으로 컴파일러가 소스 코드를 컴파일하여 오브젝트 파일(Object File)을 생성하면, 이러한 오브젝트 파일은 실행 파일을 만들기 위해 링커(Linker)에 의해 결합됩니다.
링킹은 크게 두 가지 유형이 있습니다.
1. 정적 링킹(Static Linking)
정적 링킹은 컴파일된 오브젝트 파일(Object File)과 라이브러리(Library)를 결합하여 실행 파일(Executable File)을 생성하는 과정입니다. 이 때, 실행 파일은 모든 함수와 라이브러리를 포함하므로, 런타임에 추가적인 라이브러리 로딩이 필요하지 않습니다.
정적 링킹의 장점은 실행 파일이 의존성 문제를 일으키지 않으며, 이식성이 높아진다는 것입니다. 그러나 단점은 실행 파일의 크기가 커질 수 있으며, 여러 프로그램에서 동일한 라이브러리를 사용할 때, 메모리 낭비가 발생할 수 있다는 것입니다.
예를 들어, C 프로그램에서 "math" 라이브러리를 사용하여 삼각함수를 계산한다고 가정해봅시다.
이 경우, 다음과 같은 과정으로 링킹이 수행됩니다.
1. 컴파일러는 소스 코드를 컴파일하여 오브젝트 파일을 생성합니다.
이때, "math.h" 헤더 파일을 포함하여 "sin", "cos", "tan" 함수 등을 호출하면,
컴파일러는 오브젝트 파일에 함수를 호출하는 코드를 생성합니다.
2. 링커는 "math" 라이브러리를 포함하여 실행 파일을 생성합니다.
이때, "math" 라이브러리의 함수가 필요하면, 링커는 "math" 라이브러리에서 해당 함수를 찾아 오브젝트 파일에 결합합니다.
이때, 정적 링킹을 사용한다면, 링커는 "math" 라이브러리에서 해당 함수를 직접 찾아서 실행 파일에 포함시킵니다.
3. 실행 파일이 실행될 때, 필요한 라이브러리와 함수가 이미 실행 파일 내에 포함되어 있으므로,
런타임에 추가적인 라이브러리 로딩이 필요하지 않습니다.
따라서, 정적 라이브러리를 사용하는 경우, 모든 함수와 라이브러리를 실행 파일에 포함시켜 런타임에 추가적인 라이브러리 로딩을 방지하고, 실행 파일의 이식성을 높일 수 있습니다.
2. 동적 링킹(Dynamic Linking)
동적 링킹은 런타임에 필요한 라이브러리를 로드하여 실행 파일(Executable File)을 생성하는 과정입니다. 이 때, 실행 파일은 필요한 라이브러리만 포함하므로, 실행 파일의 크기가 줄어들고, 여러 프로그램에서 공유할 수 있습니다.
동적 링킹의 장점은 실행 파일의 크기가 작고, 메모리 절약 효과가 있다는 것입니다. 그러나 단점은 런타임에 추가적인 라이브러리 로딩이 필요하므로, 실행 시간이 더 오래 걸릴 수 있다는 것입니다.
동적 라이브러리는 실행 파일과 별도로 존재하는 라이브러리로, 실행 파일이 라이브러리를 필요로 할 때에만 로딩되며, 메모리에 올라가서 사용됩니다. 이러한 동적 라이브러리는 링커에 의해 실행 파일에 포함되는 것이 아니라, 실행 파일이 로딩될 때 동적으로 링킹됩니다.
동적 라이브러리 링킹 과정은 크게 두 단계로 나눌 수 있습니다.
첫 번째 단계는 라이브러리를 로딩하는 과정이며, 두 번째 단계는 라이브러리를 실행 파일과 링킹하는 과정입니다.
첫 번째 단계에서는 운영 체제가 라이브러리를 찾아서 메모리에 로딩합니다.
예를 들어, "math" 라이브러리를 동적 라이브러리로 만들어서 사용한다고 가정해봅시다. 이때, 컴파일러가 소스 코드를 컴파일하여 오브젝트 파일을 생성하는 단계에서, "math" 라이브러리의 함수를 호출하는 코드가 생성됩니다. 하지만, 링커는 "math" 라이브러리를 실행 파일에 포함시키지 않습니다.
대신, 실행 파일이 로딩될 때, "math" 라이브러리가 필요한 경우, 운영 체제는 "math" 라이브러리를 로딩하여 해당 함수들을 실행 파일과 링킹합니다. (이 단계에서는 운영 체제가 라이브러리를 찾아서 메모리에 로딩합니다. 이때, 라이브러리는 운영 체제가 지정한 경로에서 찾을 수도 있고, 실행 파일이나 다른 라이브러리에 의해 이미 로딩된 경우에는 해당 라이브러리를 공유하여 사용할 수도 있습니다.)
두 번째 단계에서는 실행 파일과 라이브러리를 링킹합니다.
이때, 실행 파일은 라이브러리가 제공하는 함수와 변수를 사용할 수 있습니다. 이 과정에서는 라이브러리가 제공하는 함수와 변수의 이름과 주소를 실행 파일의 심볼 테이블에 등록합니다. 이후 실행 파일에서 라이브러리의 함수를 호출하면, 실행 파일은 등록된 주소를 참조하여 해당 함수를 실행합니다.
동적 라이브러리를 사용하면, 라이브러리를 로딩하는 시점에 메모리 사용량이 증가하며, 실행 파일과 동적 라이브러리가 모두 필요한 경우 초기 로딩 시간도 더 걸리지만, 실행 파일의 크기를 줄일 수 있으며, 여러 개의 실행 파일에서 동일한 라이브러리를 사용할 경우, 해당 라이브러리의 코드와 데이터가 메모리에 한 번만 로딩되므로 디스크 공간의 낭비를 줄일 수 있습니다.(실행파일과 라이브러리의 호환성 문제가 발생 할 수 있음) 또한, 라이브러리를 업데이트하면, 모든 실행 파일을 다시 컴파일하지 않아도 되며, 새로운 버전의 라이브러리를 로딩하는 실행 파일만 수정하면 됩니다.
링킹은 컴파일러와 링커에 의해 자동으로 수행되지만, 경우에 따라 명시적으로 링커를 호출하여 링킹을 수행할 수도 있습니다. 링킹 과정에서는 중복된 기능을 가진 오브젝트 파일과 라이브러리를 제거하고, 링크 주소를 결정하여 실행 파일을 생성합니다