Header dan implementasi examplelib pada bagian 1-4 berupa source code. Pada bagian ini kita membungkus source tersebut menjadi dua bentuk library siap pakai: static library libexamplelib.a dan shared library libexamplelib.so. Tujuan bagian ini adalah memahami perbedaan keduanya, kapan memakai masing-masing, dan bagaimana meng-link konsumen ke library yang telah dibangun.

Konsep #

Static library (.a) adalah arsip object file yang dimasukkan ke executable pada saat link time. Seluruh kode library menjadi bagian dari executable — ukuran executable besar, tetapi tidak ada dependensi runtime. Keuntungan: portable (executable berdiri sendiri), tidak perlu khawatir versi library berubah. Kerugian: setiap executable memuat salinan kode library — boros disk bila banyak executable memakai library yang sama.

Shared library (.so) adalah kode yang dimuat ke memori pada saat runtime oleh dynamic linker. Eksekutable kecil (kode library tidak ikut), dan banyak executable dapat berbagi satu salinan library di memori. Keuntungan: hemat disk dan memori, library dapat diupdate tanpa rebuild executable (cukup ganti .so). Kerugian: dependensi runtime — executable tidak bisa berjalan bila .so tidak ada atau versi salah.

Mekanisme pembuatan static library: compile source ke object file (gcc -c -fPIC examplelib.c -o examplelib.o), lalu arsip dengan ar rcs libexamplelib.a examplelib.o. Flag -fPIC (Position Independent Code) tidak wajib untuk static lib tetapi praktik baik agar object file dapat juga dipakai untuk shared lib. Mekanisme shared library: compile dengan -fPIC lalu link dengan -shared: gcc -shared -o libexamplelib.so examplelib.c -lm. Flag -lm diperlukan karena library memakai sqrt dari <math.h>.

Implementasi #

Makefile lengkap dengan target static, shared, dan test:

makefile
CC       = gcc
CFLAGS   = -std=c11 -Wall -Wextra -pedantic -fPIC -Iinclude
ARFLAGS  = rcs
LDFLAGS  = -lm

LIB_STATIC  = libexamplelib.a
LIB_SHARED  = libexamplelib.so
TEST_BIN    = test_examplelib

SRC_LIB = src/examplelib.c
SRC_TEST = tests/test_examplelib.c

.PHONY: all static shared test clean install

all: static shared test

static: $(LIB_STATIC)

shared: $(LIB_SHARED)

# Build static library (.a)
$(LIB_STATIC): $(SRC_LIB)
        $(CC) $(CFLAGS) -c -o src/examplelib.o $(SRC_LIB)
        ar $(ARFLAGS) $@ src/examplelib.o

# Build shared library (.so)
$(LIB_SHARED): $(SRC_LIB)
        $(CC) $(CFLAGS) -shared -o $@ $(SRC_LIB) $(LDFLAGS)

# Build test, link to static lib explicitly
test: $(TEST_BIN)
        ./$(TEST_BIN)

$(TEST_BIN): $(SRC_TEST) $(LIB_STATIC)
        $(CC) $(CFLAGS) -o $@ $(SRC_TEST) -L. -l:libexamplelib.a $(LDFLAGS)

clean:
        rm -f $(LIB_STATIC) $(LIB_SHARED) $(TEST_BIN) src/*.o

Program konsumen demo_consumer.c yang memakai examplelib:

c
#include <stdio.h>
#include <stdint.h>
#include "examplelib.h"

int main(void) {
    printf("Versi examplelib: %s\n", exlib_version());

    double data[] = {1.0, 2.0, 3.0, 4.0, 5.0};
    double mean = 0.0;
    exlib_status s = exlib_mean(data, 5, &mean);
    printf("Mean [1,2,3,4,5] = %.2f (status %d = %s)\n",
           mean, (int)s, exlib_strerror(s));

    uint8_t bytes[] = {1, 2, 3, 4, 5};
    exlib_reverse_bytes(bytes, 5);
    printf("Reverse bytes: %d %d %d %d %d\n",
           bytes[0], bytes[1], bytes[2], bytes[3], bytes[4]);

    char buf[32];
    exlib_format_size(1572864ULL, buf, sizeof(buf));
    printf("Format 1572864 bytes: %s\n", buf);

    exlib_status found = exlib_contains("hello world", "world");
    printf("Contains 'world' in 'hello world': %s\n",
           found == EXLIB_OK ? "YA" : "TIDAK");
    return 0;
}

Build dengan static library dan jalankan:

bash
gcc -std=c11 -Wall -Wextra -pedantic -Iinclude -o demo_static \
    demo_consumer.c -L. -l:libexamplelib.a -lm
./demo_static

Output yang Dihasilkan:

text
Versi examplelib: 1.0.0
Mean [1,2,3,4,5] = 3.00 (status 0 = sukses)
Reverse bytes: 5 4 3 2 1
Format 1572864 bytes: 1.50 MB
Contains 'world' in 'hello world': YA

Build dengan shared library:

bash
gcc -std=c11 -Wall -Wextra -pedantic -Iinclude -o demo_shared \
    demo_consumer.c -L. -lexamplelib -lm -Wl,-rpath,.
./demo_shared

Output yang Dihasilkan (sama persis dengan static):

text
Versi examplelib: 1.0.0
Mean [1,2,3,4,5] = 3.00 (status 0 = sukses)
Reverse bytes: 5 4 3 2 1
Format 1572864 bytes: 1.50 MB
Contains 'world' in 'hello world': YA

Pengujian #

Verifikasi perbedaan dependensi runtime antara static dan shared:

bash
echo "=== ldd demo_static ==="
ldd demo_static | grep -E "examplelib|libc"
echo "=== ldd demo_shared ==="
ldd demo_shared | grep -E "examplelib|libc"

Output yang Dihasilkan:

text
=== ldd demo_static ===
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x...)
=== ldd demo_shared ===
        libexamplelib.so => ./libexamplelib.so (0x...)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x...)

Perbedaan jelas: executable static hanya bergantung pada libc, sementara executable shared juga bergantung pada libexamplelib.so. Bila file .so dihapus atau dipindahkan, executable shared tidak bisa berjalan.

Verifikasi simbol yang diekspor shared library:

bash
nm -D libexamplelib.so | grep "T exlib_"

Output yang Dihasilkan:

text
0000000000001540 T exlib_contains
0000000000001424 T exlib_format_size
00000000000011b9 T exlib_mean
000000000000138e T exlib_reverse_bytes
0000000000001274 T exlib_stddev

Kolom T menandakan simbol di section text (kode) dan visible untuk dynamic linking. Fungsi exlib_version dan exlib_strerror juga ada (panjang output terpotong untuk ringkas). Bila ingin menyembunyikan simbol internal (mis. helper static), kompilasi dengan -fvisibility=hidden dan tandai fungsi publik dengan __attribute__((visibility("default"))).

Mari kita telaah baris-baris kunci pada Makefile dan proses link:

  1. Baris CFLAGS = -std=c11 -Wall -Wextra -pedantic -fPIC -Iinclude: flag kompilasi. -fPIC menghasilkan Position Independent Code — wajib untuk shared library, opsional tetapi aman untuk static. -Iinclude menambah direktori include/ ke search path header.
  2. Baris $(CC) $(CFLAGS) -c -o src/examplelib.o $(SRC_LIB): compile ke object file tanpa link. Flag -c memberhentikan kompilasi setelah object file; tidak ada pemanggilan linker.
  3. Baris ar $(ARFLAGS) $@ src/examplelib.o: arsip object file ke static library. ar rcs = replace, create, symbol table. Tanpa s, archive tidak punya symbol table dan link gagal.
  4. Baris $(CC) $(CFLAGS) -shared -o $@ $(SRC_LIB) $(LDFLAGS): link shared library. Flag -shared memberitahu linker menghasilkan shared object, bukan executable. Library libm (-lm) ikut karena exlib_stddev memakai sqrt.
  5. Baris -l:libexamplelib.a (link test): link ke static library secara eksplisit. Sintaks -l:filename memaksa linker memakai file spesifik, bukan mencari via konvensi -lname. Tanpa : , linker dapat memilih .so bila ada — yang menyebabkan dependensi runtime.
  6. Baris -Wl,-rpath,. (link shared): embed runtime path ke executable. -Wl, meneruskan opsi berikutnya ke linker. -rpath,. memberitahu dynamic linker mencari .so di direktori yang sama dengan executable (tanda .). Tanpa rpath, executable shared gagal dengan cannot open shared object file.
  7. Baris install -m 644 $(LIB_STATIC) $(PREFIX)/lib/ (di target install): install library ke prefix. Mode 644 (rw-r–r–) untuk static lib, 755 (rwxr-xr-x) untuk shared lib agar bisa dieksekusi (loader perlu execute permission).

Pada Bagian 6 kita akan menulis README, versioning, dan install target untuk mendistribusikan library ke konsumen lain.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

src/examplelib.c

Unduh berkas
c
/* examplelib.c - Implementasi library examplelib */
#include "examplelib.h"

#include <math.h>
#include <string.h>
#include <stdio.h>

const char *exlib_version(void) {
    return EXAMPLELIB_VERSION_STRING;
}

const char *exlib_strerror(exlib_status code) {
    switch (code) {
        case EXLIB_OK:            return "sukses";
        case EXLIB_ERR_NULL:      return "pointer NULL atau n=0";
        case EXLIB_ERR_RANGE:     return "nilai di luar rentang valid";
        case EXLIB_ERR_FORMAT:    return "format input tidak valid";
        case EXLIB_ERR_OVERFLOW:  return "potensi overflow";
        case EXLIB_ERR_NOT_FOUND: return "tidak ditemukan";
        default:                  return "kode tidak dikenal";
    }
}

exlib_status exlib_mean(const double *data, size_t n, double *out) {
    if (data == NULL || out == NULL || n == 0) {
        return EXLIB_ERR_NULL;
    }
    double sum = 0.0;
    for (size_t i = 0; i < n; i++) {
        sum += data[i];
    }
    *out = sum / (double)n;
    return EXLIB_OK;
}

exlib_status exlib_stddev(const double *data, size_t n, double *out) {
    if (data == NULL || out == NULL || n == 0) {
        return EXLIB_ERR_NULL;
    }
    double mean = 0.0;
    exlib_status s = exlib_mean(data, n, &mean);
    if (s != EXLIB_OK) {
        return s;
    }
    double sum_sq = 0.0;
    for (size_t i = 0; i < n; i++) {
        double diff = data[i] - mean;
        sum_sq += diff * diff;
    }
    *out = sqrt(sum_sq / (double)n);
    return EXLIB_OK;
}

exlib_status exlib_reverse_bytes(uint8_t *data, size_t n) {
    if (data == NULL) {
        return EXLIB_ERR_NULL;
    }
    if (n < 2) {
        return EXLIB_OK;
    }
    size_t i = 0;
    size_t j = n - 1;
    while (i < j) {
        uint8_t tmp = data[i];
        data[i] = data[j];
        data[j] = tmp;
        i++;
        j--;
    }
    return EXLIB_OK;
}

exlib_status exlib_format_size(uint64_t bytes, char *out, size_t out_cap) {
    if (out == NULL) {
        return EXLIB_ERR_NULL;
    }
    if (out_cap < 16) {
        return EXLIB_ERR_RANGE;
    }
    /* Daftar unit dari kecil ke besar. */
    static const char *units[] = {"B", "KB", "MB", "GB", "TB", "PB"};
    static const int n_units = 6;
    if (bytes == 0) {
        snprintf(out, out_cap, "0.00 B");
        return EXLIB_OK;
    }
    int unit_idx = 0;
    double size = (double)bytes;
    while (size >= 1024.0 && unit_idx < n_units - 1) {
        size /= 1024.0;
        unit_idx++;
    }
    snprintf(out, out_cap, "%.2f %s", size, units[unit_idx]);
    return EXLIB_OK;
}

exlib_status exlib_contains(const char *haystack, const char *needle) {
    if (haystack == NULL || needle == NULL) {
        return EXLIB_ERR_NULL;
    }
    if (needle[0] == '\0') {
        return EXLIB_OK;
    }
    if (strstr(haystack, needle) != NULL) {
        return EXLIB_OK;
    }
    return EXLIB_ERR_NOT_FOUND;
}

tests/test_examplelib.c

Unduh berkas
c
/* test_examplelib.c - Unit test untuk examplelib */
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "examplelib.h"

static int tests_run = 0;
static int tests_pass = 0;

#define ASSERT(cond, msg) \
    do { \
        tests_run++; \
        if (cond) { \
            tests_pass++; \
        } else { \
            printf("FAIL: %s (line %d)\n", msg, __LINE__); \
        } \
    } while (0)

static void test_version(void) {
    ASSERT(strcmp(exlib_version(), "1.0.0") == 0,
           "versi harus 1.0.0");
    ASSERT(strcmp(exlib_strerror(EXLIB_OK), "sukses") == 0,
           "strerror OK = 'sukses'");
    ASSERT(strcmp(exlib_strerror(EXLIB_ERR_NOT_FOUND),
                  "tidak ditemukan") == 0,
           "strerror NOT_FOUND sesuai");
}

static void test_mean(void) {
    double data[] = {1.0, 2.0, 3.0, 4.0, 5.0};
    double mean = 0.0;
    exlib_status s = exlib_mean(data, 5, &mean);
    ASSERT(s == EXLIB_OK, "mean harus sukses");
    ASSERT(fabs(mean - 3.0) < 1e-9, "mean = 3.0");

    s = exlib_mean(NULL, 5, &mean);
    ASSERT(s == EXLIB_ERR_NULL, "mean(NULL) = ERR_NULL");

    s = exlib_mean(data, 0, &mean);
    ASSERT(s == EXLIB_ERR_NULL, "mean n=0 = ERR_NULL");
}

static void test_stddev(void) {
    double data[] = {2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0};
    double sd = 0.0;
    exlib_status s = exlib_stddev(data, 8, &sd);
    ASSERT(s == EXLIB_OK, "stddev harus sukses");
    /* Standar deviasi populasi: sqrt(32/8) = 2.0 */
    ASSERT(fabs(sd - 2.0) < 1e-9, "stddev = 2.0");
}

static void test_reverse_bytes(void) {
    uint8_t data[] = {1, 2, 3, 4, 5};
    exlib_status s = exlib_reverse_bytes(data, 5);
    ASSERT(s == EXLIB_OK, "reverse sukses");
    ASSERT(data[0] == 5 && data[1] == 4 && data[2] == 3,
           "data setelah reverse = 5,4,3,2,1");
    ASSERT(data[3] == 2 && data[4] == 1, "data[3..4] = 2,1");

    s = exlib_reverse_bytes(NULL, 5);
    ASSERT(s == EXLIB_ERR_NULL, "reverse NULL = ERR_NULL");

    uint8_t single[] = {42};
    s = exlib_reverse_bytes(single, 1);
    ASSERT(s == EXLIB_OK, "reverse n=1 = OK");
    ASSERT(single[0] == 42, "single byte tidak berubah");
}

static void test_format_size(void) {
    char buf[32];

    exlib_status s = exlib_format_size(0, buf, sizeof(buf));
    ASSERT(s == EXLIB_OK, "format 0 = OK");
    ASSERT(strcmp(buf, "0.00 B") == 0, "0 bytes = '0.00 B'");

    s = exlib_format_size(1024ULL, buf, sizeof(buf));
    ASSERT(s == EXLIB_OK, "format 1024 = OK");
    ASSERT(strcmp(buf, "1.00 KB") == 0, "1024 = '1.00 KB'");

    s = exlib_format_size(1572864ULL, buf, sizeof(buf));
    ASSERT(s == EXLIB_OK, "format 1.5MB = OK");
    ASSERT(strcmp(buf, "1.50 MB") == 0, "1.5MB = '1.50 MB'");

    s = exlib_format_size(1073741824ULL, buf, sizeof(buf));
    ASSERT(s == EXLIB_OK, "format 1GB = OK");
    ASSERT(strcmp(buf, "1.00 GB") == 0, "1GB = '1.00 GB'");

    s = exlib_format_size(100, buf, 5);
    ASSERT(s == EXLIB_ERR_RANGE, "buffer kecil = ERR_RANGE");
}

static void test_contains(void) {
    exlib_status s = exlib_contains("hello world", "world");
    ASSERT(s == EXLIB_OK, "substring ditemukan");

    s = exlib_contains("hello world", "xyz");
    ASSERT(s == EXLIB_ERR_NOT_FOUND, "substring tidak ditemukan");

    s = exlib_contains("abc", "");
    ASSERT(s == EXLIB_OK, "needle kosong = OK");

    s = exlib_contains(NULL, "x");
    ASSERT(s == EXLIB_ERR_NULL, "NULL haystack = ERR_NULL");
}

int main(void) {
    test_version();
    test_mean();
    test_stddev();
    test_reverse_bytes();
    test_format_size();
    test_contains();

    printf("\n=== Hasil Test examplelib ===\n");
    printf("Total : %d\n", tests_run);
    printf("Lulus : %d\n", tests_pass);
    printf("Gagal : %d\n", tests_run - tests_pass);
    return (tests_run == tests_pass) ? 0 : 1;
}