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:
int x = 2147483647 + 1; // konstanta: compiler melihat langsung
Mengompilasi potongan di atas dengan g++ -std=c++23 -Wall -Wextra menghasilkan warning:
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:
#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:
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? yaBedah baris-per-baris:
- Baris 7 (
std::cout << "stok - 1 (unsigned) = " << stok - 1u):0u - 1utidak 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. - 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). - Baris 13 (
int a = std::numeric_limits<int>::max();): batas atasintdiambil daristd::numeric_limitsdi<limits>, bukan ditulis manual, agar kode tetap benar bila tipe diganti kelong long. - Baris 17 (
bool meluap = __builtin_add_overflow(a, b, &hasil);): builtin milik GCC/Clang ini menjumlahkana + b, menuliskan hasilnya (termasuk nilai wrap) kehasilmelalui pointer, dan mengembalikantruebila terjadi meluap. Perilaku builtin ini terdefinisi penuh — beda dengana + blangsung yang untuk signed adalah undefined behavior. - Baris 20 (
std::cout << "hasil wrap menurut builtin = " << hasil): nilaihasiltercetak-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). - 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 > maxdisubstitusi menjadia > max - b, danmax - bdijamin aman karenab > 0. Cabang ini tereksekusi dan tercetak karenaamemang berada di batas atas. - Baris 29-31 (
int minimum = ...; bool subOvf = ...; bool mulOvf = ...;): pengurangan dan perkalian punya builtin serupa —__builtin_sub_overflowdan__builtin_mul_overflow. Perhatikan100000 * 100000 = 10.000.000.000yang melampaui batasintsekitar 2,1 miliar, sehinggamulOvfbernilaitrue.
Strategi Mencegah Integer Overflow #
| Strategi | Kapan dipakai | Catatan |
|---|---|---|
Tipe lebih besar (long long, std::int64_t) | nilai aktual jelas muat di tipe lebih luas | pencegahan termurah; int64_t menampung sampai sekitar 9,2 triliun |
| Cek batas manual | kode portabel lintas compiler | susun ulang pertidaksamaan agar operasi perantara aman |
| Builtin overflow (GCC/Clang) | jalur panas yang perlu cepat | satu instruksi add-with-carry di CPU, tanpa cabang tambahan |
| Unsigned dengan aturan modular | bit manipulation, hash, checksum | jangan 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.