Beberapa fitur bahasa C dimulai sebagai peretasan yang kebetulan berhasil.
Beberapa tanda tangan untuk main, serta daftar argumen panjang-variabel, adalah salah satu fiturnya.
Pemrogram memperhatikan bahwa mereka dapat meneruskan argumen tambahan ke suatu fungsi, dan tidak ada hal buruk yang terjadi dengan kompiler yang diberikan.
Ini adalah kasus jika konvensi pemanggilan seperti itu:
- Fungsi pemanggilan membersihkan argumen.
- Argumen paling kiri lebih dekat ke bagian atas tumpukan, atau ke dasar bingkai tumpukan, sehingga argumen palsu tidak membatalkan pengalamatan.
Satu set konvensi pemanggil yang mematuhi aturan ini adalah pengalihan parameter berbasis tumpukan di mana pemanggil memunculkan argumen, dan mereka didorong dari kanan ke kiri:
;; pseudo-assembly-language
;; main(argc, argv, envp); call
push envp ;; rightmost argument
push argv ;;
push argc ;; leftmost argument ends up on top of stack
call main
pop ;; caller cleans up
pop
pop
Dalam kompiler di mana jenis konvensi pemanggilan ini terjadi, tidak ada kebutuhan khusus yang perlu dilakukan untuk mendukung dua jenis main, atau bahkan jenis tambahan. mainbisa menjadi fungsi tanpa argumen, dalam hal ini ia mengabaikan item yang didorong ke tumpukan. Jika ini adalah fungsi dari dua argumen, maka ia menemukan argcdan argvsebagai dua item tumpukan paling atas. Jika itu adalah varian tiga argumen khusus platform dengan penunjuk lingkungan (ekstensi umum), itu juga akan berfungsi: ia akan menemukan argumen ketiga itu sebagai elemen ketiga dari atas tumpukan.
Jadi panggilan tetap berfungsi untuk semua kasus, memungkinkan satu modul start-up tetap untuk ditautkan ke program. Modul itu bisa ditulis dalam C, dengan fungsi yang menyerupai ini:
/* I'm adding envp to show that even a popular platform-specific variant
can be handled. */
extern int main(int argc, char **argv, char **envp);
void __start(void)
{
/* This is the real startup function for the executable.
It performs a bunch of library initialization. */
/* ... */
/* And then: */
exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}
Dengan kata lain, modul start ini hanya memanggil main tiga argumen, selalu. Jika main tidak membutuhkan argumen, atau hanya int, char **, itu berfungsi dengan baik, serta jika tidak membutuhkan argumen, karena konvensi pemanggilan.
Jika Anda melakukan hal semacam ini dalam program Anda, itu akan menjadi perilaku nonportable dan dianggap tidak terdefinisi oleh ISO C: mendeklarasikan dan memanggil fungsi dengan satu cara, dan mendefinisikannya dengan cara lain. Tetapi trik startup kompiler tidak harus portabel; itu tidak dipandu oleh aturan untuk program portabel.
Tetapi anggaplah bahwa konvensi pemanggilan sedemikian rupa sehingga tidak dapat bekerja dengan cara ini. Dalam hal ini, kompilator harus memperlakukannya mainsecara khusus. Ketika ia mengetahui bahwa ia sedang mengompilasi mainfungsinya, ia dapat menghasilkan kode yang kompatibel dengan, katakanlah, panggilan tiga argumen.
Artinya, Anda menulis ini:
int main(void)
{
/* ... */
}
Tetapi ketika kompilator melihatnya, pada dasarnya ia melakukan transformasi kode sehingga fungsi yang dikompilasinya terlihat lebih seperti ini:
int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
/* ... */
}
kecuali bahwa nama __argc_ignoreitu tidak ada secara harfiah. Tidak ada nama seperti itu yang dimasukkan ke dalam cakupan Anda, dan tidak akan ada peringatan tentang argumen yang tidak digunakan. Transformasi kode menyebabkan kompilator memancarkan kode dengan hubungan yang benar yang mengetahui bahwa ia harus membersihkan tiga argumen.
Strategi implementasi lainnya adalah untuk compiler atau mungkin linker untuk membuat custom __startfungsi (atau apapun namanya), atau setidaknya pilih satu dari beberapa alternatif yang telah dikompilasi sebelumnya. Informasi dapat disimpan dalam file objek tentang bentuk yang didukung mainyang digunakan. Linker dapat melihat info ini, dan memilih versi yang benar dari modul start-up yang berisi panggilan mainyang kompatibel dengan definisi program. Implementasi C biasanya hanya memiliki sejumlah kecil bentuk yang didukung mainsehingga pendekatan ini layak.
Kompiler untuk bahasa C99 selalu harus memperlakukan mainsecara khusus, sampai batas tertentu, untuk mendukung peretasan yang jika fungsi berhenti tanpa returnpernyataan, perilakunya seolah-olah return 0dijalankan. Ini, sekali lagi, dapat ditangani dengan transformasi kode. Kompilator memperhatikan bahwa fungsi yang dipanggil mainsedang dikompilasi. Kemudian ia memeriksa apakah ujung tubuh berpotensi dijangkau. Jika demikian, itu menyisipkanreturn 0;
mainmetode dalam satu program diC(atau, sebenarnya, dalam hampir semua bahasa dengan konstruksi seperti itu).