Variabel dan Tipe Data

Tipe dasar int, char, float, dan double, aturan ukuran tipe, serta pengantar overflow bilangan bulat.

Daftar isi

Variabel adalah nama yang mengaitkan satu lokasi memori dengan sebuah tipe, sehingga nilai dapat disimpan, dibaca, dan diubah sepanjang program berjalan. Topik ini membahas cara mendeklarasikan dan menginisialisasi variabel, memperkenalkan tipe data dasar C beserta ukurannya, lalu menunjukkan apa yang terjadi ketika nilai melampaui rentang yang sanggup ditampung tipe tersebut. Setelah membacanya, tipe yang tepat untuk setiap kebutuhan data dapat dipilih dengan alasan yang jelas.

Deklarasi, inisialisasi, dan penugasan#

Deklarasi variabel menyatakan nama dan tipe yang dipakai, sedangkan inisialisasi memberi nilai pertama pada saat yang sama. Bentuk umumnya adalah tipe nama = nilai;, dan beberapa variabel bertipe sama dapat dideklarasikan dalam satu baris. Deklarasi tanpa inisialisasi berbentuk tipe nama; juga sah, tetapi isinya belum ditentukan sebagaimana dijelaskan di bagian akhir topik. Nama variabel berlaku sejak baris deklarasinya sampai akhir kurung kurawal tempat deklarasi itu berada, sehingga dua blok berbeda boleh memuat variabel bernama sama tanpa saling mengganggu. Setelah dideklarasikan, isi variabel dapat diganti melalui penugasan (assignment) dengan tanda =:

c deklarasi.c
#include <stdio.h>

int main(void) {
    int tinggi = 170;
    int berat = 65;
    double suhu = 28.5;
    char inisial = 'B';

    printf("Tinggi : %d cm\n", tinggi);
    printf("Berat  : %d kg\n", berat);
    printf("Suhu   : %.1f derajat Celsius\n", suhu);
    printf("Inisial: %c\n", inisial);
    return 0;
}
text Keluaran
Tinggi : 170 cm
Berat  : 65 kg
Suhu   : 28.5 derajat Celsius
Inisial: B

Perhatikan bahwa penentu format pada printf harus sesuai dengan tipe variabelnya, yaitu %d untuk int, %f untuk tipe pecahan, dan %c untuk char. Kepatuhan pada aturan ini penting karena C tidak memeriksa kecocokan penentu format dan tipe pada saat program berjalan. Variabel yang dideklarasikan tanpa inisialisasi tetap bisa digunakan, tetapi isinya adalah sisa data acak dari memori, sehingga membaca variabel yang belum diberi nilai merupakan sumber bug yang sulit dilacak.

Tipe bilangan bulat dan ukurannya#

C menyediakan beberapa tipe bilangan bulat dengan ukuran berbeda, yaitu char, short, int, dan long. Ukuran sebuah tipe menentukan rentang nilai yang sanggup ditampungnya, karena tipe berukuran n bit mampu mewakili sebanyak 2 pangkat n pola bit yang berbeda. Ukuran tipe dapat dilihat dengan operator sizeof, yang menghasilkan jumlah byte yang dipakai tipe tersebut pada mesin tempat program dijalankan:

c ukuran-dan-rentang.c
#include <stdio.h>
#include <limits.h>

int main(void) {
    printf("sizeof(char)   = %zu byte\n", sizeof(char));
    printf("sizeof(short)  = %zu byte\n", sizeof(short));
    printf("sizeof(int)    = %zu byte\n", sizeof(int));
    printf("sizeof(long)   = %zu byte\n", sizeof(long));
    printf("sizeof(float)  = %zu byte\n", sizeof(float));
    printf("sizeof(double) = %zu byte\n", sizeof(double));

    printf("INT_MIN = %d\n", INT_MIN);
    printf("INT_MAX = %d\n", INT_MAX);
    printf("UINT_MAX = %u\n", UINT_MAX);
    return 0;
}
text Keluaran
sizeof(char)   = 1 byte
sizeof(short)  = 2 byte
sizeof(int)    = 4 byte
sizeof(long)   = 8 byte
sizeof(float)  = 4 byte
sizeof(double) = 8 byte
INT_MIN = -2147483648
INT_MAX = 2147483647
UINT_MAX = 4294967295

Nilai-nilai di atas berlaku pada mesin Linux x86-64. Standar C sebenarnya tidak menetapkan ukuran persis, melainkan batas minimumnya, sehingga int selalu berukuran minimal 2 byte tetapi lazimnya 4 byte pada komputer modern. Penentu %zu dipakai karena hasil sizeof bertipe size_t, dan konstanta seperti INT_MAX berasal dari berkas header limits.h yang menyimpan batas setiap tipe bilangan bulat.

Bilangan bulat juga dibedakan menjadi bertanda (signed) dan tak bertanda (unsigned). Tipe bertanda membelah rentangnya untuk bilangan negatif dan positif, sehingga int berukuran 4 byte mencakup sekitar minus dua miliar hingga plus dua miliar. Tipe tak bertanda seperti unsigned int memakai seluruh bit untuk bilangan nol ke atas, sehingga rentangnya mencapai 4294967295 sebagaimana ditunjukkan UINT_MAX di atas.

Tipe pecahan: float dan double#

Tipe float dan double menyimpan bilangan pecahan dengan representasi titik mengambang (floating point). Keduanya berbeda pada tingkat ketelitiannya, yaitu float berukuran 4 byte sedangkan double berukuran 8 byte. Perbedaan ukuran itu terlihat nyata ketika angka yang tidak dapat direpresentasikan persis disimpan:

c pecahan.c
#include <stdio.h>

int main(void) {
    float f = 1.0f / 3.0f;
    double d = 1.0 / 3.0;

    printf("float  : %.10f\n", f);
    printf("double : %.10f\n", d);
    printf("0.1 + 0.2 == 0.3 -> %d\n", 0.1 + 0.2 == 0.3);
    return 0;
}
text Keluaran
float  : 0.3333333433
double : 0.3333333333
0.1 + 0.2 == 0.3 -> 0

Angka satu per tiga tidak pernah tersimpan persis, dan tingkat ketelitian float habis lebih cepat daripada double. Baris ketiga memperlihatkan akibat yang lebih mengejutkan, yaitu 0.1 + 0.2 ternyata tidak sama dengan 0.3, sehingga perbandingannya bernilai 0. Nilai 0.1 memang tidak dapat direpresentasikan persis dalam biner, dan penyimpangan kecil itu menumpuk setiap kali dihitung. Karena itu, dua nilai pecahan tidak pernah dibandingkan langsung dengan ==. Pembandingan yang aman menguji selisih keduanya terhadap batas kecil yang ditoleransi, misalnya menulis kondisi yang menerima selisih di bawah 0.000001.

Sebagai aturan praktis, gunakan double untuk pekerjaan pecahan sehari-hari dan sisakan float untuk kasus yang benar-benar mengutamakan penghematan memori, misalnya array berukuran besar.

Tipe char#

Tipe char berukuran 1 byte dan semula dirancang untuk menyimpan satu karakter. Isi yang sebenarnya tersimpan adalah kode angka karakter tersebut menurut tabel ASCII, sehingga 'A' tersimpan sebagai angka 65. Sifat ganda ini membuat char dapat dicetak sebagai karakter dengan %c maupun sebagai angka dengan %d, dan tipe ini juga layak dipakai untuk bilangan bulat kecil.

Program deklarasi.c di atas sudah memperlihatkan pencetakan sebagai karakter, yaitu inisial dicetak dengan %c menghasilkan huruf B. Apabila 'B' + 1 dievaluasi, hasilnya adalah angka 67 karena penjumlahan bekerja pada kode angkanya. Angka itu dicetak sebagai karakter menjadi C. Kebiasaan ini menjadi dasar teknik seperti pengubahan huruf kecil menjadi huruf besar yang dipakai di banyak program teks. Karakter noncetak juga direpresentasikan dengan notasi khusus yang sama seperti pada string, misalnya '\n' untuk pindah baris dan '\t' untuk tab, sehingga char dan literal string berbagi satu daftar notasi.

Ketika nilai melampaui rentang: pengantar overflow#

Setiap tipe memiliki rentang terbatas, dan pertanyaannya adalah apa yang terjadi ketika hasil hitungan keluar dari rentang itu. Untuk tipe tak bertanda, standar C menetapkan perilakunya secara jelas, yaitu nilai berputar seolah-olah perhitungan dilakukan dengan aritmetika modulo. Program berikut menunjukkan perputaran tersebut pada unsigned int:

c overflow.c
#include <stdio.h>

int main(void) {
    unsigned int stok = 0;

    printf("Stok awal           : %u\n", stok);
    stok = stok - 1;
    printf("Setelah dikurangi 1 : %u\n", stok);
    stok = stok + 1;
    printf("Setelah ditambah 1  : %u\n", stok);
    return 0;
}
text Keluaran
Stok awal           : 0
Setelah dikurangi 1 : 4294967295
Setelah ditambah 1  : 0

Mengurangi nol dengan satu pada tipe tak bertanda menghasilkan nilai terbesarnya, yaitu 4294967295, dan menambah kembalinya satu mengembalikan nilai ke nol. Perilaku ini dikenal sebagai overflow pada tipe tak bertanda dan nilainya memang terdefinisi. Kondisi berbeda berlaku untuk tipe bertanda seperti int:

Karena itu, pilih tipe tak bertanda bila perhitungan memang secara logis tidak boleh negatif, dan perbesar tipe, misalnya dari int ke long, bila rentangnya berisiko terlampaui.

Kesalahan umum#

Kesalahan pertama adalah menganggap ukuran tipe sama di semua tempat. long berukuran 8 byte pada Linux x86-64 tetapi berukuran 4 byte pada Windows, sehingga program yang menyimpan angka besar di long bisa bekerja di satu sistem lalu gagal di sistem lain. Untuk kebutuhan ukuran yang pasti, berkas header stdint.h menyediakan tipe berukuran tetap seperti int32_t dan int64_t.

Kesalahan kedua adalah membaca variabel yang belum diberi nilai. Isinya adalah sisa memori yang nilainya berubah-ubah antar eksekusi, sehingga bug ini tidak tampak saat diuji tetapi muncul di lain waktu. Biasakan memberi nilai awal pada setiap deklarasi, sebagaimana dilakukan pada seluruh program contoh di topik ini.

Kesalahan ketiga adalah mencetak nilai dengan penentu format yang tidak cocok, misalnya %d untuk variabel double. Kompilasi tetap berhasil karena printf tidak memeriksa kecocokan tipe argumennya, tetapi keluaran menjadi kacau atau program berhenti. Memilih penentu format sesuai tipe dan mengompilasi dengan -Wall adalah dua kebiasaan yang menutup celah ini.

Ringkasan#

  • Variabel menyatakan nama bertipe yang menampung satu nilai, dan inisialisasi memberi nilai pertama agar isi variabel tidak acak.
  • Ukuran tipe menentukan rentang nilainya, dan sizeof serta header limits.h digunakan untuk melihat ukuran dan batas tersebut.
  • double lebih teliti daripada float, dan nilai pecahan tidak pernah dibandingkan dengan ==.
  • char menyimpan kode angka karakter, sehingga dapat dicetak sebagai huruf maupun angka.
  • Pada tipe tak bertanda, melewati batas membuat nilai berputar, sedangkan pada tipe bertanda peristiwa itu tak terdefinisi.

Referensi#