Proyek-proyek sebelumnya menghasilkan aplikasi monolitik: satu executable dengan banyak tanggung jawab. Pada proyek ini, kita belajar membangun library C yang reusable — sebuah pustaka fungsi yang bisa dipakai oleh banyak program lain tanpa duplikasi kode. Kita akan bangun examplelib, library kecil dengan utilitas statistik, manipulasi byte, dan formatting ukuran file. Library ini diuji, didistribusikan dalam dua bentuk (static .a dan shared .so), dan dijelaskan dengan README serta CMakeLists.

Program yang akan kita bangun terdiri dari beberapa komponen:

  • Header publik include/examplelib.h berisi deklarasi API: konstanta versi, enum kode error, dan prototype fungsi.
  • Implementasi src/examplelib.c berisi definisi fungsi yang dapat di-link ke executable lain.
  • Unit test tests/test_examplelib.c memverifikasi setiap fungsi API.
  • Build system ganda: Makefile untuk build manual dengan gcc+ar, dan CMakeLists.txt untuk build portabel dengan CMake.

Tampilan Program Akhir #

Hasil akhir proyek adalah sekumpulan file yang bisa dipakai sebagai library:

text
$ ls -la
examplelib/
├── include/examplelib.h
├── src/examplelib.c
├── tests/test_examplelib.c
├── Makefile
├── CMakeLists.txt
└── README.md

$ make all
gcc -std=c11 ... -fPIC -c src/examplelib.c -o src/examplelib.o
ar rcs libexamplelib.a src/examplelib.o
gcc -std=c11 ... -shared -o libexamplelib.so src/examplelib.c -lm
gcc -std=c11 ... -o test_examplelib tests/test_examplelib.c -L. -lexamplelib -lm
./test_examplelib

=== Hasil Test examplelib ===
Total : 28
Lulus : 28
Gagal : 0

$ ./test_examplelib
=== Hasil Test examplelib ===
Total : 28
Lulus : 28
Gagal : 0

Setelah build, kita bisa menulis program lain yang meng-include examplelib.h dan meng-link ke libexamplelib.a atau libexamplelib.so untuk memakai fungsinya.

Tujuan Pembelajaran #

Setelah menyelesaikan proyek ini, kita mampu:

  • merancang public API yang bersih dengan pemisahan header/implementation,
  • memvalidasi argumen dan mengembalikan kode error eksplisit (enum),
  • membangun static library .a dengan ar dan shared library .so dengan gcc -shared,
  • menulis CMakeLists.txt yang menghasilkan config-file package untuk konsumen lain,
  • mendistribusikan library dengan README, versioning, dan install target.

Prasyarat #

Proyek ini memperkenalkan konsep baru seputar packaging. Bab yang dirujuk:

  • fungsi dan modularitas — pemisahan .h/.c dan best practice header guard,
  • manajemen memori — pilihan antara stack/heap untuk return value,
  • build systems: Makefile dan CMake — automatasi build multi-file,
  • error handling patterns — kode error sebagai enum dengan dokumentasi,
  • standar C: C11 — fitur seperti _Static_assert dan _Noreturn.

Tahapan Pengerjaan #

Proyek dikerjakan dalam enam bagian berurutan:

  1. Bagian 1: Desain Public API — rancang header examplelib.h dengan konstanta, enum, dan prototype.
  2. Bagian 2: Implementasi Library — tulis examplelib.c dengan validasi argumen dan kode error.
  3. Bagian 3: Error Handling — strategi kode error eksplisit dengan enum dan exlib_strerror.
  4. Bagian 4: Unit Testing — tulis test runner yang memverifikasi setiap fungsi API.
  5. Bagian 5: Static dan Shared Library — bangun libexamplelib.a dan libexamplelib.so.
  6. Bagian 6: Dokumentasi dan Release — tulis README, versioning, dan install target.

Mulailah dari Bagian 1 dan ikuti urutannya. Setiap bagian dibangun di atas bagian sebelumnya. Berkas sumber tiap bagian tersedia di bagian Berkas Sumber di bawah halaman masing-masing.

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;
}