Semua contoh sejauh ini memakai karakter ASCII—huruf Latin dasar, angka, dan tanda baca standar yang muat dalam 7-bit (0-127). Pada aplikasi nyata, kita sering berhadapan dengan teks multibyte: huruf beraksen seperti é atau ñ, karakter non-Latin seperti kanji atau arab, hingga emoji. C modern mengasumsikan bahwa char adalah byte tunggal, dan string char * adalah urutan byte tanpa struktur tambahan—artinya standard library tidak tahu apakah byte-byte tersebut adalah ASCII murni, UTF-8, Latin-1, atau encoding lain. Yang menentukan makna byte adalah locale dan konvensi aplikasi. Pada chapter ini kita akan menelusuri ASCII sebagai baseline, UTF-8 sebagai encoding paling umum di sistem modern, dan tipe wchar_t yang merupakan alternatif karakter lebar (wide character).
ASCII: Karakter 7-bit #
ASCII (American Standard Code for Information Interchange) mendefinisikan 128 karakter dengan kode 0-127. Karakter 0-31 adalah control characters (newline, tab, bell), 32 adalah spasi, 48-57 adalah digit 0-9, 65-90 adalah huruf kapital A-Z, 97-122 adalah huruf kecil a-z, dan sisanya tanda baca. Karakter di atas 127 (high-bit set) tidak didefinisikan oleh ASCII—pemakaian byte-byte tersebut bergantung pada code page atau encoding yang dipakai.
#include <stdio.h>
#include <string.h>
int main(void) {
/* ASCII: 0-127 (7-bit), karakter di bawah 128 */
char ascii[] = "Hello";
printf("ASCII string: %s (panjang %zu)\n", ascii, strlen(ascii));
/* Tabel ASCII: tampilkan karakter 32..126 */
printf("Tabel ASCII (32..126):\n");
for (int i = 32; i <= 126; i++) {
printf(" %3d = '%c'\n", i, (char)i);
}
return 0;
}Output yang Dihasilkan (20 baris pertama):
ASCII string: Hello (panjang 5)
Tabel ASCII (32..126):
32 = ' '
33 = '!'
34 = '"'
35 = '#'
36 = '$'
37 = '%'
38 = '&'
39 = '''
40 = '('
41 = ')'
42 = '*'
43 = '+'
44 = ','
45 = '-'
46 = '.'
47 = '/'
48 = '0'
49 = '1'Loop di atas mengiterasi kode 32-126 dan mencetak setiap kode beserta karakter yang direpresentasikannya. Kode 32 adalah spasi ' ', kode 48-57 adalah digit '0'-'9', kode 65-90 adalah huruf kapital 'A'-'Z', dan kode 97-122 adalah huruf kecil 'a'-'z'. Selisih antara huruf kecil dan kapital adalah konstan: 'a' - 'A' = 32. Inilah mengapa operasi case conversion sederhana dapat dilakukan dengan c + ('a' - 'A') atau c | 0x20 (dengan asumsi c sudah huruf).
UTF-8: Encoding Multibyte Dominan #
UTF-8 adalah encoding yang paling umum dipakai di sistem modern (Linux, macOS, web). UTF-8 menggunakan 1-4 byte per karakter: 1 byte untuk ASCII (kode 0-127, sehingga UTF-8 kompatibel mundur dengan ASCII), 2 byte untuk karakter Latin tambahan (é, ñ, ü), 3 byte untuk karakter Asia (kanji, hangul, devanagari), dan 4 byte untuk karakter supplemental (emoji, simbol matematis langka). Konsekuensinya, strlen pada string UTF-8 mengembalikan jumlah byte, bukan jumlah karakter (code point).
#include <stdio.h>
#include <string.h>
int main(void) {
/* UTF-8: karakter non-ASCII disimpan sebagai multibyte.
Source file disimpan dalam UTF-8. */
const char *s = "Halo, Budi!"; /* pure ASCII */
const char *u = "café"; /* é = 2 byte UTF-8 */
const char *j = "日本語"; /* 3 byte per karakter */
printf("ASCII: %s\n", s);
printf("Panjang \"%s\" dalam byte = %zu\n", s, strlen(s));
printf("\nUTF-8 dengan é: %s\n", u);
printf("Panjang dalam byte = %zu (bukan jumlah karakter!)\n", strlen(u));
printf("\nKanji: %s\n", j);
printf("Panjang dalam byte = %zu\n", strlen(j));
/* Tampilkan byte per byte untuk é */
printf("\nByte untuk 'é': ");
for (size_t i = 0; i < strlen(u); i++) {
printf("%02X ", (unsigned char)u[i]);
}
printf("\n");
return 0;
}Output yang Dihasilkan:
ASCII: Halo, Budi!
Panjang "Halo, Budi!" dalam byte = 11
UTF-8 dengan é: café
Panjang dalam byte = 5 (bukan jumlah karakter!)
Kanji: 日本語
Panjang dalam byte = 9
Byte untuk 'é': 63 61 66 C3 A9 Mari kita telaah baris-baris kode di atas:
- Baris 8-11 (deklarasi tiga string): ketiga string literal ditulis langsung di source file. Saat compiler membaca source UTF-8, byte-byte literal
caféditulis apa adanya:c,a,f,0xC3,0xA9. Byte0xC3 0xA9adalah representasi UTF-8 dari karakter é (U+00E9). - Baris 13-14 (strlen untuk ASCII):
"Halo, Budi!"berisi 11 byte murni ASCII.strlen= 11 = jumlah karakter karena setiap karakter adalah 1 byte. - Baris 17-18 (strlen untuk é):
"café"memiliki 4 karakter tetapi 5 byte karena é menempati 2 byte.strlen= 5. Inilah inti masalah UTF-8 denganstrlen: fungsi tersebut menghitung byte, bukan code point. - Baris 21-22 (strlen untuk kanji):
"日本語"memiliki 3 karakter tetapi 9 byte karena setiap kanji menempati 3 byte di UTF-8.strlen= 9. - Baris 25-28 (loop byte per byte): kita mencetak setiap byte dalam heksadesimal. Hasil:
63 61 66 C3 A9—63=c,61=a,66=f, laluC3 A9= é. Pola UTF-8 untuk 2-byte: byte pertama dimulai dengan bit pattern110xxxxx(0xC0-0xDF), byte kedua10xxxxxx(0x80-0xBF).
Menghitung Code Point Manual dan wchar_t #
Berikut dua pendekatan untuk menghitung jumlah karakter (code point) di string UTF-8: decode manual dengan inspeksi leading byte, atau konversi ke wchar_t lewat mbstowcs.
#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
#include <string.h>
int main(void) {
setlocale(LC_ALL, "");
/* wchar_t: tipe karakter lebar (biasanya 4 byte di Linux) */
wchar_t teks[] = L"Belajar"; /* pure ASCII */
printf("Panjang karakter wide: %zu\n", wcslen(teks));
printf("sizeof wchar_t: %zu byte\n", sizeof(wchar_t));
/* Hitung jumlah code point dari string UTF-8 (manual decode) */
const char *s = "café"; /* 4 karakter, 5 byte */
size_t code_points = 0;
for (size_t i = 0; s[i] != '\0'; ) {
unsigned char c = (unsigned char)s[i];
if (c < 0x80) i += 1; /* ASCII */
else if ((c & 0xE0) == 0xC0) i += 2; /* 2-byte */
else if ((c & 0xF0) == 0xE0) i += 3; /* 3-byte */
else if ((c & 0xF8) == 0xF0) i += 4; /* 4-byte */
else i += 1; /* byte invalid */
code_points++;
}
printf("String UTF-8: \"%s\"\n", s);
printf("Jumlah byte: %zu\n", strlen(s));
printf("Jumlah code point: %zu\n", code_points);
return 0;
}Output yang Dihasilkan:
Panjang karakter wide: 7
sizeof wchar_t: 4 byte
String UTF-8: "café"
Jumlah byte: 5
Jumlah code point: 4Pada contoh pertama, wchar_t teks[] = L"Belajar"; mendeklarasikan array karakter lebar. Setiap elemen adalah wchar_t (4 byte di Linux, mengikuti UCS-4/UTF-32). wcslen(teks) = 7 karena setiap karakter adalah satu elemen array—tidak ada multibyte sequence. Pada contoh kedua, kita menghitung code point string UTF-8 "café" dengan cara inspeksi leading byte: byte pertama < 0x80 berarti karakter 1-byte (ASCII); byte dengan pola 110xxxxx (0xC0-0xDF) memulai karakter 2-byte; 1110xxxx (0xE0-0xEF) memulai 3-byte; 11110xxx (0xF0-0xF7) memulai 4-byte. Hasil: 4 code point untuk "café" meskipun panjang byte-nya 5.
Ringkasan Konsep #
String di C adalah urutan byte tanpa struktur encoding bawaan. ASCII mendominasi karakter dasar (0-127, 7-bit) dan menjadi baseline kompatibilitas mundur UTF-8. UTF-8 menggunakan 1-4 byte per karakter: 1 byte untuk ASCII, 2 byte untuk Latin tambahan, 3 byte untuk karakter Asia, 4 byte untuk emoji. Konsekuensi praktis: strlen pada string UTF-8 mengembalikan jumlah byte, bukan jumlah karakter. Operasi s[i] mungkin mengakses byte tengah dari multibyte sequence dan menghasilkan karakter rusak. Tipe wchar_t (4 byte di Linux) adalah alternatif di mana setiap elemen array adalah satu code point, tetapi memerlukan setup locale dan library untuk pemrosesan yang proper. Pada chapter berikutnya kita akan membahas aspek keamanan pemrosesan string—termasuk buffer overflow yang menjadi salah satu celah keamanan paling sering dijumpai di program C.