Aritmetika integer di komputer selalu bekerja pada ruang nilai yang terbatas: int pada platform ini hanya menampung -2147483648 sampai 2147483647, dan hasil penjumlahan yang melewati batas itu tidak bisa disimpan apa adanya. Keputusan desain C++ untuk kejadian ini berbeda tajam antara dua keluarga tipe: unsigned akan berputar modular dengan aturan yang terdefinisi penuh, sedangkan signed yang meluap adalah undefined behavior (perilaku tak terdefinisi) — program boleh menghasilkan angka apa pun, boleh crash, atau bahkan dianggap tidak pernah meluap oleh optimizer. Karena Anda tidak bisa mengandalkan hasil signed yang meluap, satu-satunya strategi yang benar adalah mendeteksi kemungkinan meluap sebelum operasi dijalankan. Chapter ini membedah kedua aturan itu, memperkenalkan __builtin_add_overflow dari GCC sebagai alat deteksi, dan menutup dengan checklist memilih tipe yang lebih besar.

Dua Aturan untuk Satu Kejadian #

Unsigned bekerja seperti odometer: melewati nilai terbesar membuatnya kembali ke nol, dan turun di bawah nol membuatnya mendarat di nilai terbesar. Matematisnya, semua operasi dihitung modulo 2^N (N = jumlah bit), sehingga hasilnya selalu terdefinisi dan bisa diprediksi — sifat yang bahkan dimanfaatkan untuk hash dan checksum. Signed tidak punya jaring pengaman seperti itu. Standar C++ menyatakan hasil aritmetika signed yang meluap tidak terdefinisi, dan compiler modern memakai kebebasan itu untuk optimasi: asumsi “signed tidak pernah meluap” bisa memangkas cabang if yang Anda tulis untuk memeriksa meluap.

Kabar baiknya, kasus meluap yang bisa dilihat compiler pada waktu kompilasi diwarnai. Lihat perbedaannya pada potongan bermasalah ini:

cpp Potongan bermasalah — bukan bagian bundle
int x = 2147483647 + 1;   // konstanta: compiler melihat langsung

Mengompilasi potongan di atas dengan g++ -std=c++23 -Wall -Wextra menghasilkan warning:

text Contoh output compiler
warning: integer overflow in expression of type 'int' results in '-2147483648' [-Woverflow]

Warning ini muncul karena kedua operan berupa konstanta sehingga nilainya dihitung saat kompilasi. Ketika operan berasal dari variabel — data dari pengguna, file, atau sensor — compiler tidak bisa memprediksi dan program Anda yang harus bertanggung jawab. Di situlah cek eksplisit menjadi wajib.

Mendeteksi Meluap dengan Cek Batas dan Builtin #

Program berikut menyatukan semua teknik: perilaku modular unsigned, deteksi lewat __builtin_add_overflow, dan cek batas manual yang portabel ke compiler mana pun:

cpp
#include <iostream>
#include <limits>

int main() {
    // 1. Unsigned: modular, perilakunya terdefinisi dan bisa diprediksi
    unsigned stok = 0;
    std::cout << "stok - 1 (unsigned) = " << stok - 1u << "\n";

    unsigned maks = 4294967295u;               // nilai terbesar 32-bit unsigned
    std::cout << "maks + 1 (unsigned) = " << maks + 1u << "\n";

    // 2. Signed: meluap = undefined behavior, jadi WAJIB dideteksi dulu
    int a = std::numeric_limits<int>::max();   // 2147483647
    int b = 1;
    int hasil = 0;

    bool meluap = __builtin_add_overflow(a, b, &hasil);
    std::cout << "add " << a << " + " << b << " meluap? "
              << (meluap ? "ya" : "tidak") << "\n";
    std::cout << "hasil wrap menurut builtin = " << hasil << "\n";

    // 3. Cek batas manual sebelum operasi (portabel, tanpa builtin)
    if (b > 0 && a > std::numeric_limits<int>::max() - b) {
        std::cout << "cek batas: " << a << " + " << b
                  << " akan melebihi int\n";
    }

    // 4. Pengurangan dan perkalian punya builtin masing-masing
    int minimum = std::numeric_limits<int>::lowest();  // -2147483648
    bool subOvf = __builtin_sub_overflow(minimum, 1, &hasil);
    bool mulOvf = __builtin_mul_overflow(100000, 100000, &hasil);
    std::cout << "sub meluap? " << (subOvf ? "ya" : "tidak")
              << ", mul meluap? " << (mulOvf ? "ya" : "tidak") << "\n";

    return 0;
}

Output yang Dihasilkan:

text
stok - 1 (unsigned) = 4294967295
maks + 1 (unsigned) = 0
add 2147483647 + 1 meluap? ya
hasil wrap menurut builtin = -2147483648
cek batas: 2147483647 + 1 akan melebihi int
sub meluap? ya, mul meluap? ya

Bedah baris-per-baris:

  1. Baris 7 (std::cout << "stok - 1 (unsigned) = " << stok - 1u): 0u - 1u tidak menghasilkan -1, melainkan berputar ke 4294967295 — nilai terbesar unsigned 32-bit. Inilah aritmetika modular: hasilnya terdefinisi, tetapi hampir selalu bukan yang Anda maksud secara bisnis.
  2. Baris 9-10 (unsigned maks = 4294967295u; ...): menambah 1 ke batas atas memutar kembali ke 0. Kedua arah perputaran ini legal, hanya perlu disadari ketika Anda memang sengaja memakai unsigned (misalnya bit manipulation).
  3. Baris 13 (int a = std::numeric_limits<int>::max();): batas atas int diambil dari std::numeric_limits di <limits>, bukan ditulis manual, agar kode tetap benar bila tipe diganti ke long long.
  4. Baris 17 (bool meluap = __builtin_add_overflow(a, b, &hasil);): builtin milik GCC/Clang ini menjumlahkan a + b, menuliskan hasilnya (termasuk nilai wrap) ke hasil melalui pointer, dan mengembalikan true bila terjadi meluap. Perilaku builtin ini terdefinisi penuh — beda dengan a + b langsung yang untuk signed adalah undefined behavior.
  5. Baris 20 (std::cout << "hasil wrap menurut builtin = " << hasil): nilai hasil tercetak -2147483648. Ini bukan hasil “benar” secara matematis — kita mencetaknya justru untuk menunjukkan bahwa builtin menyimpan nilai wrap yang bisa Anda gunakan untuk bereaksi (misalnya mengembalikan error atau menjenurkan ke batas).
  6. Baris 23-26 (if (b > 0 && a > std::numeric_limits<int>::max() - b)): cek batas manual portabel. Idenya menghindari penjumlahan yang bisa meluap dengan menyusun ulang pertidaksamaan: a + b > max disubstitusi menjadi a > max - b, dan max - b dijamin aman karena b > 0. Cabang ini tereksekusi dan tercetak karena a memang berada di batas atas.
  7. Baris 29-31 (int minimum = ...; bool subOvf = ...; bool mulOvf = ...;): pengurangan dan perkalian punya builtin serupa — __builtin_sub_overflow dan __builtin_mul_overflow. Perhatikan 100000 * 100000 = 10.000.000.000 yang melampaui batas int sekitar 2,1 miliar, sehingga mulOvf bernilai true.

Strategi Mencegah Integer Overflow #

StrategiKapan dipakaiCatatan
Tipe lebih besar (long long, std::int64_t)nilai aktual jelas muat di tipe lebih luaspencegahan termurah; int64_t menampung sampai sekitar 9,2 triliun
Cek batas manualkode portabel lintas compilersusun ulang pertidaksamaan agar operasi perantara aman
Builtin overflow (GCC/Clang)jalur panas yang perlu cepatsatu instruksi add-with-carry di CPU, tanpa cabang tambahan
Unsigned dengan aturan modularbit manipulation, hash, checksumjangan dipakai sekadar untuk “menghindari” cek nilai bisnis

Aturan praktis yang bisa langsung dipakai: mulai dari tipe yang cukup luas untuk rentang data Anda — penghitung populasi pakai std::int64_t, bukan int — lalu tambahkan deteksi eksplisit di batas sistem yang menerima input eksternal. Pembagian dua integer yang menghasilkan nol (1 / 2 → 0) adalah kejutan aritmetika integer lain yang berdekatan dengan topik ini, tetapi kita menyimpannya untuk chapter operator; sekarang kita berpindah ke keluarga tipe dengan aturan meluap yang jauh lebih halus: floating-point, tempat “meluap” bukan satu-satunya bahaya — kehilangan presisi terjadi hampir di setiap penjumlahan.

Rujukan resmi untuk topik Chapter ini: overview numerik C++ dan fundamental types di cppreference, serta Integer Overflow Builtins di manual GCC.