Topik ini memetakan tempat setiap unsur program C berada selama program berjalan, mulai dari kode, data global, alokasi dinamis, hingga variabel lokal. Arah pertumbuhan heap dan stack dibuktikan dengan perbandingan alamat yang keluarannya stabil berupa 0 atau 1. Alat size dan nm kemudian dipakai untuk membaca pembagian segmen pada berkas objek hasil kompilasi. Setelah topik ini selesai, deklarasi C apa pun dapat dihubungkan dengan wilayah memorinya pada peta proses.
Lima wilayah memori proses#
Memori sebuah proses terbagi menjadi lima wilayah dengan tugas yang berbeda. Wilayah text menyimpan instruksi mesin program dan bersifat baca saja, sedangkan wilayah data menyimpan global yang diberi nilai awal. Wilayah bss menampung global tanpa nilai awal, dan isinya diisi nol oleh sistem saat program dimuat. Wilayah heap melayani permintaan memori melalui malloc saat program berjalan dan berada tepat di atas keduanya, sedangkan wilayah stack menampung variabel lokal beserta data setiap panggilan fungsi. Kelima wilayah tersusun berurutan pada ruang alamat (address space) proses, yaitu deretan alamat yang dilihat oleh program.
Diagram berikut menata kelima wilayah dari alamat rendah di bagian bawah hingga alamat tinggi di bagian atas, lengkap dengan arah pertumbuhan heap dan stack.
Arah pertumbuhan heap dan stack berlawanan, dan itulah alasan keduanya digambar saling mendekat. Heap bertambah ke arah alamat yang lebih tinggi ketika malloc meminta ruang baru, sedangkan stack bertambah ke arah alamat yang lebih rendah setiap kali fungsi dipanggil. Ruang kosong di antara keduanya menjadi tempat pertumbuhan masing-masing, dan sistem operasi menambah ukuran wilayahnya saat ruang itu mulai habis. Tabel berikut merangkum isi dan arah pertumbuhan kelima wilayah tersebut.
| Wilayah | Isi | Arah pertumbuhan |
|---|---|---|
| text | instruksi mesin program | tetap |
| data | global dengan nilai awal | tetap |
| bss | global tanpa nilai awal, diisi nol | tetap |
| heap | alokasi dinamis malloc | ke alamat lebih tinggi |
| stack | variabel lokal dan data panggilan fungsi | ke alamat lebih rendah |
Tiga wilayah pertama berukuran tetap sejak program dimuat, sedangkan heap dan stack terus bergerak selama program berjalan.
Membuktikan arah pertumbuhan dengan perbandingan alamat#
Program memori.c berikut mencetak ukuran tipe, nilai dua global, dan empat perbandingan alamat yang keluarannya berupa 0 atau 1. Fungsi dalam_fungsi menerima alamat variabel lokal main lalu membandingkannya dengan alamat variabel lokal miliknya sendiri, sehingga hasilnya menyatakan posisi relatif dua wilayah stack. Perbandingan sengaja dipilih daripada pencetakan alamat mentah, dan alasannya dijelaskan pada peringatan sesudah program.
#include <stdio.h>
#include <stdlib.h>
int global_terisi = 5;
int global_kosong;
static int dalam_fungsi(int *patokan) {
int lokal_dalam = 0;
return &lokal_dalam < patokan;
}
int main(void) {
int lokal_main = 7;
char *heap_satu = malloc(16);
char *heap_dua = malloc(64);
printf("sizeof(int) = %zu\n", sizeof(int));
printf("sizeof(int *) = %zu\n", sizeof(int *));
printf("global_terisi = %d\n", global_terisi);
printf("global_kosong = %d\n", global_kosong);
printf("stack tumbuh turun = %d\n", dalam_fungsi(&lokal_main));
printf("heap di atas data = %d\n", heap_satu > (char *)&global_terisi);
printf("heap bertambah ke atas = %d\n", heap_dua > heap_satu);
printf("stack di atas heap = %d\n", (char *)&lokal_main > heap_dua);
free(heap_satu);
free(heap_dua);
return 0;
}sizeof(int) = 4
sizeof(int *) = 8
global_terisi = 5
global_kosong = 0
stack tumbuh turun = 1
heap di atas data = 1
heap bertambah ke atas = 1
stack di atas heap = 1Keempat perbandingan bernilai 1 pada sandbox. Wilayah stack untuk panggilan dalam_fungsi berada di alamat lebih rendah daripada wilayah stack main, sehingga stack terbukti tumbuh ke bawah pada x86-64. heap_satu yang diminta lebih dahulu berada di bawah heap_dua, sehingga heap terbukti bertambah ke atas, dan keduanya berada di atas wilayah data. Stack menempati posisi paling atas karena alamat &lokal_main melampaui kedua alamat heap. Ukuran tipe juga terbaca dari keluaran, yaitu int sebesar 4 byte dan pointer sebesar 8 byte pada x86-64. Nilai global_terisi tetap 5 sesuai inisialisasi, sedangkan global_kosong bernilai 0 tanpa inisialisasi apa pun karena wilayah bss diisi nol oleh sistem.
Ukuran segmen pada berkas objek#
# kompilasi memori.c menjadi berkas objek
gcc -c memori.c -o memori.o
# tampilkan ukuran segmen text, data, dan bss
size memori.o text data bss dec hex filename
678 4 4 686 2ae memori.oAlat size membaca pembagian tiga segmen tetap pada berkas objek dan menampilkannya dalam satu tabel. Kolom text berisi 678 byte kode mesin dari main dan dalam_fungsi, kolom data berisi 4 byte untuk global_terisi, dan kolom bss berisi 4 byte untuk global_kosong. Kolom dec dan hex hanya menjumlahkan ketiganya dalam dua notasi angka. Keluaran ini memperlihatkan hal yang sering mengejutkan, yaitu wilayah bss tidak disimpan dalam berkas karena isinya nol semuanya, sehingga assembler cukup mencatat ukurannya dan sistem mengisi nol saat program dimuat. Kebutuhan memori program yang berjalan karena itu selalu lebih besar daripada ukuran berkasnya. Wilayah heap dan stack tidak muncul pada tabel karena keduanya baru dibentuk setelah program mulai dieksekusi, dan nilai tabel ini deterministik untuk berkas sumber serta compiler yang sama.
Daftar simbol pada berkas objek#
# daftar simbol berkas objek beserta kategori wilayahnya
nm memori.o0000000000000000 t dalam_fungsi
U free
0000000000000000 B global_kosong
0000000000000000 D global_terisi
000000000000001f T main
U malloc
U printfAlat nm mendaftar simbol, yaitu nama-nama yang dikenal assembler dan linker, beserta huruf kategori wilayahnya. Huruf T, D, dan B menandai simbol publik pada text, data, dan bss, sedangkan huruf kecil t pada dalam_fungsi muncul karena fungsi itu dideklarasikan static, sehingga namanya hanya terlihat di berkasnya sendiri. Tiga simbol berhuruf U berarti belum terdefinisi, yaitu malloc, printf, dan free, dan ketiganya baru akan diisi linker dengan alamat dari library standar saat program utuh dibentuk. Alamat pada kolom kiri masih 0 atau angka kecil karena berkas objek belum ditautkan, terlihat dari main yang berada pada jarak 31 byte dari awal segmen text. Daftar U memperlihatkan sisi lain tugas linker, yaitu mencocokkan nama yang dipakai dengan nama yang disediakan oleh bagian program lain. Pemeriksaan nm juga berguna ketika kode sumber tidak tersedia, karena pembagian wilayahnya tetap terbaca dari simbolnya.
Kesalahan umum#
Tiga kekeliruan berikut sering muncul saat tata letak memori mulai diperiksa.
- Menyamakan ukuran berkas dengan kebutuhan memori proses. Wilayah bss tidak disimpan dalam berkas karena isinya nol, sedangkan heap dan stack baru terbentuk saat program berjalan. Ukuran berkas karena itu tidak pernah menjadi ukuran memori yang sebenarnya, dan alat
sizeyang membaca pembagian wilayahnya lebih dapat diandalkan. - Menganggap arah pertumbuhan stack ditetapkan oleh bahasa C. Standar C tidak mengatur arah itu, sehingga keputusannya berada pada arsitektur prosesor dan sistem operasinya. Pada x86-64 stack tumbuh ke bawah sebagaimana dibuktikan program di atas, tetapi asumsi yang sama tidak boleh dibawa begitu saja ke arsitektur lain.
- Mengira global tanpa nilai awal berisi sisa data. Wilayah bss selalu diisi nol saat program dimuat, sehingga
global_kosongdijamin bernilai 0. Jaminan ini hanya berlaku untuk global danstatic, karena variabel lokal di stack tidak diisi nol dan isinya tidak dapat diprediksi.
Ringkasan#
- Memori proses terbagi menjadi text, data, bss, heap, dan stack, dengan heap bertambah ke alamat tinggi dan stack bertambah ke alamat rendah.
- Arah pertumbuhan dibuktikan lewat perbandingan alamat berupa 0 atau 1 karena alamat absolut berubah pada setiap eksekusi akibat ASLR.
- Alat
sizemenampilkan ukuran text, data, dan bss berkas objek, sedangkannmmendaftar simbol beserta kategori wilayahnya. - Wilayah bss tidak disimpan dalam berkas dan baru diisi nol saat program dimuat, berbeda dari variabel lokal yang isinya tak terduga.
Referensi#
- Computer Systems: A Programmer’s Perspective — Carnegie Mellon University — Membahas tata letak memori proses C secara rinci, mulai dari segmen hingga heap dan stack.
- size — GNU Binutils
— Dokumentasi alat
sizeyang menampilkan ukuran segmen berkas objek. - nm — GNU Binutils
— Dokumentasi alat
nmbeserta arti huruf kategori pada setiap simbol.