Implementation-Defined Behavior (IDB) adalah situasi di mana standar C mengizinkan compiler memilih perilaku, tetapi compiler wajib mendokumentasikan pilihannya. Berbeda dengan undefined behavior (yang tidak ada jaminan apa pun), IDB memberi konsistensi dalam satu compiler tetapi berpotensi berbeda antar compiler atau platform. Contoh klasik: sizeof(int) yang bisa 2, 4, atau 8 byte bergantung pada arsitektur. Pada chapter ini kita akan memeriksa IDB utama yang sering ditemui di kode C praktis dan cara menulis kode yang portabel tanpa mengandalkan asumsi spesifik.

IDB pada sizeof dan limits.h #

Ukuran tipe primitif tidak ditentukan oleh standar C—standar hanya mensyaratkan batas minimum. int harus mampu menyimpan setidaknya [-32767, 32767] (16 bit), tetapi ukuran aktualnya 32 bit pada sebagian besar platform modern. long di Linux 64-bit adalah 8 byte, sedangkan di Windows 64-bit tetap 4 byte (model LLP64). Header <limits.h> menyediakan konstanta yang mengikuti nilai aktual compiler.

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

int main(void) {
    /* sizeof(int) dan CHAR_BIT bervariasi per platform */
    printf("sizeof(int)    = %zu byte\n", sizeof(int));
    printf("sizeof(long)   = %zu byte\n", sizeof(long));
    printf("sizeof(void *) = %zu byte\n", sizeof(void *));
    printf("CHAR_BIT       = %d\n", CHAR_BIT);
    printf("INT_MAX        = %d\n", INT_MAX);
    printf("INT_MIN        = %d\n", INT_MIN);
    printf("LONG_MAX       = %ld\n", LONG_MAX);

    /* Sign of char: implementation-defined; di sini demo signed char */
    int val = 200;
    signed char sc = (signed char)val;
    printf("\nsigned char sc = (signed char)200 -> nilai = %d\n", (int)sc);

    /* Right shift pada signed int: implementation-defined */
    int neg = -16;
    int shifted = neg >> 1;
    printf("(-16) >> 1 = %d\n", shifted);

    return 0;
}

Output yang Dihasilkan:

text
sizeof(int)    = 4 byte
sizeof(long)   = 8 byte
sizeof(void *) = 8 byte
CHAR_BIT       = 8
INT_MAX        = 2147483647
INT_MIN        = -2147483648
LONG_MAX       = 9223372036854775807

signed char sc = (signed char)200 -> nilai = -56
(-16) >> 1 = -8

Mari kita telaah baris-baris kode di atas:

  1. Baris 6-12 (sizeof(int), sizeof(long), sizeof(void *)): ukuran tipe bergantung pada platform. Pada Linux x86-64 dengan GCC, int = 4 byte, long = 8 byte (model LP64). Pada Windows 64-bit dengan MSVC, long = 4 byte (model LLP64). Kode yang mengasumsikan sizeof(long) == 8 akan gagal di Windows.
  2. Baris 13 (CHAR_BIT): jumlah bit per byte. Standar C mensyaratkan minimum 8, tetapi platform eksotis (DSP TMS320) memiliki CHAR_BIT = 16. Kode yang mengasumsikan 8 bits per byte tidak portabel ke platform tersebut.
  3. Baris 14-16 (INT_MAX, INT_MIN, LONG_MAX): konstanta dari <limits.h> memberi batas aktual compiler. Pakai konstanta ini, bukan angka magic seperti 2147483647.
  4. Baris 19-20 (signed char sc = (signed char)200;): konversi dari int 200 ke signed char menghasilkan nilai implementation-defined karena 200 melampaui SCHAR_MAX (127). Pada x86, hasilnya -56 (two’s complement wrapping). Kompiler lain dapat menghasilkan nilai berbeda—standar hanya mensyaratkan konsistensi dalam satu kompilasi.
  5. Baris 23-24 (int shifted = neg >> 1; di mana neg = -16): right shift pada signed int negatif adalah implementation-defined. Pada x86, hasilnya arithmetic shift (mempertahankan sign), sehingga (-16) >> 1 = -8. Kompiler lain dapat memilih logical shift (mengisi dengan 0), menghasilkan nilai besar positif.

Endianness juga IDB #

Layout byte integer multi-byte di memori (endianness) adalah IDB. Pada little-endian (x86, ARM dalam mode default), byte paling tidak signifikan disimpan di alamat terendah. Pada big-endian (SPARC, PowerPC), byte paling signifikan disimpan di alamat terendah.

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

/* Demonstrasikan limits.h untuk kode portabel */
int main(void) {
    printf("Batas portabel dari <limits.h>:\n");
    printf("  CHAR_BIT = %d\n", CHAR_BIT);
    printf("  SCHAR_MIN = %d, SCHAR_MAX = %d\n", SCHAR_MIN, SCHAR_MAX);
    printf("  UCHAR_MAX = %u\n", UCHAR_MAX);
    printf("  INT_MIN = %d, INT_MAX = %d\n", INT_MIN, INT_MAX);
    printf("  UINT_MAX = %u\n", UINT_MAX);
    printf("  LONG_MIN = %ld, LONG_MAX = %ld\n", LONG_MIN, LONG_MAX);
    printf("  ULONG_MAX = %lu\n", ULONG_MAX);

    /* Endianness: implementation-defined */
    unsigned int v = 0x01020304;
    unsigned char *p = (unsigned char *)&v;
    printf("\nLayout byte dari 0x%08X: %02X %02X %02X %02X\n",
           v, p[0], p[1], p[2], p[3]);
    if (p[0] == 0x04) {
        printf("endianness: little-endian\n");
    } else if (p[0] == 0x01) {
        printf("endianness: big-endian\n");
    } else {
        printf("endianness: unknown\n");
    }

    return 0;
}

Output yang Dihasilkan:

text
Batas portabel dari <limits.h>:
  CHAR_BIT = 8
  SCHAR_MIN = -128, SCHAR_MAX = 127
  UCHAR_MAX = 255
  INT_MIN = -2147483648, INT_MAX = 2147483647
  UINT_MAX = 4294967295
  LONG_MIN = -9223372036854775808, LONG_MAX = 9223372036854775807
  ULONG_MAX = 18446744073709551615

Layout byte dari 0x01020304: 04 03 02 01
endianness: little-endian

Casting &v ke unsigned char * memungkinkan kita melihat byte per byte. Pada little-endian, byte pertama (p[0]) berisi 0x04 (paling tidak signifikan). Kode jaringan yang mengirim integer ke wire harus mengkonversi ke network byte order (big-endian) lewat htonl/htons bila tidak ingin terikat platform.

Daftar IDB Umum #

  • sizeof(int), sizeof(long), sizeof(void *): ukuran tipe.
  • CHAR_BIT: bit per byte (minimum 8).
  • Sign of char: bisa signed atau unsigned default (GCC menggunakan signed).
  • Right shift pada signed int negatif: arithmetic atau logical.
  • Konversi floating-point ke integer yang overflow: nilai tidak terdefinisi oleh implementation.
  • Layout struct (padding, alignment): implementation-defined.
  • Behavior of realloc(NULL, size) dan realloc(ptr, 0): standar menetapkan ini, tetapi versi POSIX bisa berbeda.
  • Number of bits in unsigned long (jika beda dari int).