codedua

my technote


Static Library and Shared Library

Static library

Static library còn được gọi là archive  /ˈɑːrkaɪv/ vì thực tế nó là một file chứa một hoặc nhiều object files.
Cũng vì thế mà static library có quy tắc đặt tên là lib*.a (a đại diện cho archive), ví dụ: libgnss.a libtime.a
Công cụ để tạo ra static library gọi là archiver, sử dụng thông qua command ar
Ví dụ ta có source code math_operations.cpp chứa các function tính toán cộng, trừ, nhân, chia đơn giản như sau:

#include <stdexcept>

int add(int a, int b) {
    return a + b;
}

int sub(int a, int b) {
    return a - b;
}

int mul(int a, int b) {
    return a*b;
}

float divide(int a, int b) {
    if (a == 0) {
        throw std::invalid_argument("Division by zero error");
    } else {
        return float(a/b);
    }
}

Và một file source code advanced_operations.cpp chứa các funtion tính giai thừa (factorial) và tính số fibonacci như sau:

#include <stdexcept>

uint64_t factorial(int n) {
    if (n < 0) {
        throw std::invalid_argument("Negative number for factorial");
    }
    uint64_t result = 1;
    for (int i = 1; i <= n; ++i) {
        result *= i;
    }
    return result;
}

int fibonacci(int n) {
    if (n < 0) {
        throw std::invalid_argument("Negative number for fibonacci");
    }
    if (n == 0)
        return 0;
    if (n == 1)
        return 1;
    int a = 0, b = 1, c = 0;
    for (int i = 2; i <= n; ++i) {
        c = a + b;
        a = b;
        b = c;
    }
    return c;
}

Ta muốn compile 2 source code trên một static library libmathop.a

Biên dịch một static library

Biên dịch 2 source code trên thành 2 object file *.o
g++ -c ./math_operations.cpp ./advanced_operations.cp

compress to an archive
ar rcs libmathop.a math_operations.o advanced_operations.o

Sử dụng static library

Sử dụng thư viện tĩnh libmathop.a ta vừa biên dịch trong một source code khác là calculator.cpp.calculator.cpp sẽ gọi các functions của libmathop.a, vì vậy, trong calculator.cpp cần khai báo các function prototype ta sẽ gọi, sau đó ta mới gọi tới các function đó:

#include <iostream>

/* khai báo các functions tồn tại trong libmathop.a mà ta sẽ gọi */
int add(int, int);
int sub(int a, int b);
int mul(int a, int b);
float divide(int a, int b);

uint64_t factorial(int n);
int fibonacci(int n);

int main() {
    /* gọi tới các functions trong libmathop.a */
    std::cout << add(3, 5) << std::endl;
    std::cout << sub(3, 5) << std::endl;
    std::cout << mul(3, 5) << std::endl;
    std::cout << divide(3, 5) << std::endl;

    std::cout << factorial(10) << std::endl;
    std::cout << fibonacci(10) << std::endl;

    return 0;
}

Build chương trình calculator kết hợp từ source code calculator.cpp và thư viện tĩnh libmathop.a

g++ calculator.cpp -L. -lmathop -o calculator 
-L chỉ định đường dẫn tới libmathop.a
-l chỉ định tên static library, do ta đặt tên theo quy chuẩn lib*.a nên khi chỉ định tên static lib có thể bỏ tiền tố lib và đuôi mở rộng .a 

Bình luận

Trên thực tế, khi sử dụng một library, người ta sẽ không cần khai báo các function tồn tại trong library đó vào source code như cách calculator.cpp làm ở trên. Thay vào đó, chỉ cần include header mà library cung cấp vào source code. Điều đó đồng nghĩa với việc khi cung cấp một library, ta thường cung cấp một thư viện đã được biên dịch sẵn (libmathop.a) đi kèm với các file header tương ứng (math_operations.h, advanced_operations.h) chứa các public function prototype mà library đó có. Điều này đảm bảo tính Encapsulation (đóng gói & che giấu thông tin) và tính Abstraction (trừu tượng). Tuy nhiên, để đơn giản hóa, trong post này ta không sử dụng các file header.

Tại sao calculator.cpp không chỉ đơn giản include trực tiếp file code math_operations.cpp, advanced_operation.cpp để gọi các function tính toán mà phải sử dụng một static library?

→ để đảm bảo encapsulation…


Shared library

Đối với chương trình calculator sử dụng static library ở trên, các object file của library libmathop.a được nhúng trực tiếp vào file thực thi (executable) của chương trình calculator, nói cách khác, các object file trong thư viện tĩnh trở thành một phần của file thực thi cuối  cùng. Điều này dẫn tới các nhược điểm sau:

  • Làm tăng kích thước file thực thi cuối cùng
  • Mỗi lần chạy chương trình calculator, các object file (math_operations.o và advanced_operations.o) đều được load vào virtual memory, → nếu chạy nhiều chương trình calculator cùng lúc thì sẽ phải load nhiều lần cùng một bản sao của các object file đó gây tốn memory
  • Khi một source code của object file thay đổi (math_operations.cpp hoặc advanced_operations.cpp), ta cần compile lại object file tương ứng → do đó, calculator cũng cần build lại để link lại tới object file mới

→ Shared library có thể khắc phục những nhược điểm trên.

Ý tưởng chính của shared library là không nhúng trực tiếp các object file của library vào tệp thực thi cuối cùng (executable) mà chỉ tạo ra một liên kết tham chiếu giữa tệp thực thi và các object file cần thiết. Khi đó, chỉ cần một bản copy của object file cho tất các các chương trình nào cần link tới shared library đó. Khi chương trình calculator đầu tiên được khởi chạy, các object file của shared library được load vào memory. Khi chương trình calculator thứ hai (hoặc bất kì chương trình nào có link tới shared library đó) được khởi chạy thì không cần load lại các object file của library vào memory nữa mà dùng chung các object file đã có trong memory trước đó.

Biên dịch một shared library

Shared library nên đặt nên theo quy chuẩn lib*.so. (so = shared object).
Build shared library sử dụng câu lệnh
g++ -fPIC -shared -o libmathop.so math_operations.cpp advanced_operations.cpp
-fPIC để generate position-independent code

Sử dụng shared library

Khi build chương trình calculator sử dụng shared library libmathop.so vẫn cần bước static linking nhưng thay vì nhúng toàn bộ object files vào file thực thi (executable) thì compiler chỉ nhúng tên của shared library vào executable
g++ -o calculator calculator.cpp libmathop.so -Wl,-rpath,.
-Wl, để chỉ định pass các option đứng ngay sau nó cho linker. đó là option -rpath,.
-rpath để chỉ định đường dẫn chứa shared library, trường hợp này đường dẫn là thư mục hiện tại (.)
Khi khởi chạy chương trình calculator, một cơ chế gọi là dynamic linking (được đảm nhiệm bởi dynamic linker) sẽ tìm và load shared library tương ứng với tên shared library đã được nhúng vào executable.

Bình luận

Sử dụng static library hay dynamic library?

Một vài trường hợp dùng static lib sẽ phù hợp hơn, ví dụ, khi 1 app dùng static lib, toàn bộ code của static lib đó được nhúng vào app thì sẽ thuận tiện cho user khi sử dụng app, . Bởi vì nếu dùng shared lib thì quá trình cài đặt app sẽ cần cài đặt shared lib mà 1 số trường hợp user không có quyền cài đặt shared lib vào hệ thống. (thường để cài share lib vào các standard directories thì cần quyền sudo) hoặc trường hợp app run trên môi trường mà share lib là ko available