Integer promotions adalah konversi otomatis yang dilakukan compiler C pada tipe bertipe kecil—char, short, dan tipe enumerasi—menjadi tipe int (atau unsigned int dalam kasus khusus) sebelum operator apa pun diterapkan. Banyak pemula terkejut mengetahui bahwa ekspresi short x = 30000; short y = 30000; short hasil = x + y; sebenarnya tidak menjumlahkan dua short secara langsung; kedua operand dipromosikan ke int lebih dulu, dijumlahkan dalam int, lalu hasil int dikonversi kembali ke short saat disimpan. Pada Chapter ini kita akan menelaah aturan integer promotions secara mendalam, melihat bagaimana promosi ini mencegah overflow yang seharusnya tidak terjadi, serta bagaimana promosi memengaruhi operator unary seperti ~ dan -.

Aturan Integer Promotions #

ISO/IEC 9899 §6.3.1.1 mengatur bahwa tipe dengan integer conversion rank lebih rendah dari int (yaitu char, signed char, unsigned char, short, unsigned short, dan tipe enumerasi) secara otomatis dipromosikan ke int pada konteks berikut:

  1. Operand operator aritmetika (+, -, *, /, %).
  2. Operand operator bitwise (&, |, ^, ~, <<, >>).
  3. Operand operator unary +, -, ~.
  4. Operand operator relasional dan logika.
  5. Operand pertama dari operator conditional ? :.
  6. Argumen fungsi variadic (sebagai bagian dari default argument promotions).

Pemilihan tipe tujuan: jika int dapat merepresentasikan semua nilai tipe asal, promosi ke int; jika tidak (misalnya unsigned short pada platform 16-bit di mana int juga 16-bit), promosi ke unsigned int.

c
#include <stdio.h>

int main(void) {
    char a = 200;        /* rentang char hanya -128..127 (signed) atau 0..255 (unsigned) */
    char b = 100;
    /* Ekspresi a + b: keduanya dipromosikan ke int */
    int sum = a + b;
    printf("char a = %d, b = %d\n", (int)a, (int)b);
    printf("a + b (dipromosikan ke int) = %d\n", sum);

    /* short + short di int */
    short x = 30000;
    short y = 30000;
    int hasil = x + y;
    printf("\nshort x = %d, y = %d\n", (int)x, (int)y);
    printf("x + y (di int) = %d (tidak overflow int!)\n", hasil);

    /* Jika hasil disimpan kembali ke short, terjadi overflow */
    short hasil_short = x + y;     /* int -> short, mod 2^16 */
    printf("disimpan ke short = %d\n", (int)hasil_short);
    return 0;
}

Output yang Dihasilkan:

text
char a = -56, b = 100
a + b (dipromosikan ke int) = 44

short x = 30000, y = 30000
x + y (di int) = 60000 (tidak overflow int!)
disimpan ke short = -5536

Mari kita telaah baris-baris kode di atas:

  1. Baris 4 (char a = 200;): literal 200 melebihi rentang signed char (-128..127); compiler mengonversi 200 ke representasi char yang menjadi -56 (karena two’s complement).
  2. Baris 8 (int sum = a + b;): kedua operand a dan b dipromosikan dari char ke int sebelum dijumlahkan; (-56) + 100 = 44; hasil int 44 disimpan ke sum.
  3. Baris 14 (int hasil = x + y;): kedua short dipromosikan ke int; 30000 + 30000 = 60000 dijumlahkan dalam int (yang memiliki rentang hingga sekitar 2 miliar). Hasil 60000 tidak meluap int.
  4. Baris 18 (short hasil_short = x + y;): walaupun penjumlahan dilakukan dalam int menghasilkan 60000, hasil ini disimpan kembali ke short (rentang -32768..32767); nilai 60000 melampaui rentang short, sehingga disimpan sebagai -5536 (modulo 2^16 diinterpretasikan sebagai signed).

Promosi signed dan unsigned char #

Perilaku promosi berbeda untuk signed char versus unsigned char. Karena int cukup besar untuk menampung semua nilai keduanya, keduanya dipromosikan ke int. Namun nilai hasil promosi berbeda: signed char negatif tetap negatif sebagai int, sedangkan unsigned char positif tetap positif sebagai int.

c
#include <stdio.h>

int main(void) {
    /* char sebagai signed: rentang -128..127 */
    signed char sc = 200;     /* terpotong ke -56 */
    /* char sebagai unsigned: rentang 0..255 */
    unsigned char uc = 200;

    printf("signed char sc = 200 -> %d\n", (int)sc);
    printf("unsigned char uc = 200 -> %d\n", (int)uc);

    /* Hasil promosi ke int */
    int promo_sc = sc;
    int promo_uc = uc;
    printf("promosi signed char   -> int %d\n", promo_sc);
    printf("promosi unsigned char -> int %d\n", promo_uc);

    /* Promosi pada unary operator ~ */
    unsigned char mask = 0x0F;     /* 0000 1111 */
    unsigned int inverted = ~mask;
    printf("\nunsigned char 0x%02X di-invert:\n", mask);
    printf("  ~mask sebagai unsigned int = 0x%08X\n", inverted);
    printf("  (lihat bit atas semua 1 karena promosi)\n");
    return 0;
}

Output yang Dihasilkan:

text
signed char sc = 200 -> -56
unsigned char uc = 200 -> 200
promosi signed char   -> int -56
promosi unsigned char -> int 200

unsigned char 0x0F di-invert:
  ~mask sebagai unsigned int = 0xFFFFFFF0
  (lihat bit atas semua 1 karena promosi)

Pada baris ke-19, ekspresi ~mask terlihat seolah-olah membalik 8 bit 0x0F menjadi 0xF0. Namun karena mask bertipe unsigned char, ia dipromosikan ke int terlebih dahulu (32-bit) menjadi 0x0000000F, baru di-invert menjadi 0xFFFFFFF0. Inilah sebabnya operator ~ pada tipe kecil selalu menghasilkan bit 1 di seluruh bit atas—efek yang sering mengejutkan pemula. Untuk mendapatkan 0xF0 saja, kita perlu cast eksplisit: (unsigned char)~mask.

Promosi pada Operator Unary #

Operator unary -, +, dan ~ juga memicu integer promotions. Operator negasi - pada unsigned short menghasilkan int, bukan unsigned short. Inilah mengapa -us di mana us bertipe unsigned short tidak otomatis berubah menjadi 0xFFFF atau nilai negatif; promosi ke int membuat hasilnya 0 (jika us == 0) atau nilai negatif dalam int.

c
#include <stdio.h>
#include <limits.h>

int main(void) {
    /* Demonstrasikan integer promotion pada unary minus */
    unsigned short us = 0;
    int neg = -us;   /* us dipromosikan ke int (karena short) lalu di-negate */
    printf("unsigned short 0, -us = %d (int)\n", neg);

    /* Perbandingan: short vs int constant */
    short s = -1;
    int i = -1;
    if (s == -1) {
        printf("short -1 == int -1 (setelah promosi)\n");
    }

    /* Promosi pada bitwise shift */
    unsigned char c = 1;
    unsigned long shifted = ((unsigned int)c) << 24;  /* eksplisit promosi */
    printf("1 << 24 = 0x%lX\n", shifted);

    /* Tanpa promosi (cast ke unsigned char), shift akan terpotong */
    unsigned char trunc = (unsigned char)(c << 24);
    printf("(unsigned char)(1 << 24) = 0x%02X (terpotong ke 0)\n", trunc);

    return 0;
}

Output yang Dihasilkan:

text
unsigned short 0, -us = 0 (int)
short -1 == int -1 (setelah promosi)
1 << 24 = 0x1000000
(unsigned char)(1 << 24) = 0x00 (terpotong ke 0)

Pada baris ke-7, ekspresi -us dengan us = 0 menghasilkan 0; karena us dipromosikan ke int lebih dulu menjadi 0, lalu di-negate menjadi 0 (negasi nol tetap nol). Pada baris ke-13, perbandingan s == -1 memicu promosi s ke int; karena s = -1 dan -1 (sebagai literal int) sama, perbandingan menghasilkan 1. Pada baris ke-18, cast eksplisit (unsigned int)c memaksa promosi sebelum shift; hasil 1 << 24 adalah 16777216 (0x1000000) yang muat dalam unsigned long. Pada baris ke-22, hasil c << 24 (yang sebenarnya 16777216 di int) di-cast kembali ke unsigned char sehingga 24 bit atas terpotong, menyisakan 0x00 di byte terendah.

Mengapa Integer Promotions Ada #

Aturan integer promotions bukan keputusan arbitrer; aturan ini lahir dari pertimbangan praktis:

  • Efisiensi historis: CPU 32-bit dan 64-bit modern bekerja paling efisien pada operand berukuran int. Pemrosesan tipe 8-bit atau 16-bit sering kali membutuhkan instruksi tambahan untuk ekstensi tanda dan masking.
  • Konsistensi: hasil operator seperti ~, -, dan + pada tipe kecil yang sama selalu menghasilkan tipe yang konsisten (int), terlepas dari arsitektur target.
  • Pencegahan overflow tak terduga: tanpa promosi, char(127) + char(1) akan meluap menjadi -128 segera setelah penjumlahan; promosi ke int menghasilkan 128 yang valid.

Dengan pemahaman integer promotions, kita siap melangkah ke topik berikutnya: usual arithmetic conversions yang menentukan tipe akhir ketika dua operand bertipe berbeda (misalnya int + long atau int + double).