Saya membaca "The C Programming Language" K & R dan menemukan pernyataan ini [Pendahuluan, hal. 3]:
Karena tipe data dan struktur kontrol yang disediakan oleh C didukung langsung oleh sebagian besar komputer , pustaka run-time yang diperlukan untuk mengimplementasikan program mandiri kecil.
Apa maksud dari pernyataan yang dicetak tebal? Apakah ada contoh tipe data atau struktur kontrol yang tidak didukung langsung oleh komputer?
Jawaban:
Ya, ada tipe data yang tidak didukung secara langsung.
Pada banyak sistem tertanam, tidak ada unit floating point perangkat keras. Jadi, ketika Anda menulis kode seperti ini:
Itu diterjemahkan menjadi sesuatu seperti ini:
Kemudian compiler atau pustaka standar harus menyediakan implementasinya
_float_add()
, yang menghabiskan memori pada sistem tertanam Anda. Jika Anda menghitung byte pada sistem yang sangat kecil, ini bisa bertambah.Contoh umum lainnya adalah integer 64-bit (
long long
dalam standar C sejak 1999), yang tidak secara langsung didukung oleh sistem 32-bit. Sistem SPARC lama tidak mendukung perkalian integer, jadi perkalian harus disediakan oleh runtime. Ada contoh lain.Bahasa lainnya
Sebagai perbandingan, bahasa lain memiliki bahasa primitif yang lebih rumit.
Misalnya, simbol Lisp membutuhkan banyak dukungan runtime, seperti tabel di Lua, string dengan Python, array di Fortran, dan lain-lain. Jenis yang setara di C biasanya bukan bagian dari pustaka standar sama sekali (tidak ada simbol atau tabel standar) atau mereka jauh lebih sederhana dan tidak memerlukan banyak dukungan waktu proses (array di C pada dasarnya hanya pointer, string yang diakhiri nul adalah hampir sesederhana).
Struktur kontrol
Struktur kontrol penting yang hilang dari C adalah penanganan pengecualian. Keluar nonlokal terbatas pada
setjmp()
danlongjmp()
, yang hanya menyimpan dan memulihkan bagian tertentu dari status prosesor. Sebagai perbandingan, runtime C ++ harus berjalan di tumpukan dan memanggil destruktor dan penangan pengecualian.sumber
Sebenarnya, saya berani bertaruh bahwa isi pengantar ini tidak banyak berubah sejak 1978 ketika Kernighan dan Ritchie pertama kali menulisnya di Edisi Pertama buku, dan mereka merujuk pada sejarah dan evolusi C pada waktu itu lebih dari modern. implementasi.
Komputer pada dasarnya hanyalah bank memori dan prosesor sentral, dan setiap prosesor beroperasi menggunakan kode mesin; bagian dari desain setiap prosesor adalah arsitektur set instruksi, yang disebut Bahasa Majelis , yang memetakan satu-ke-satu dari satu set mnemonik yang dapat dibaca manusia ke kode mesin, yang semuanya angka.
Penulis bahasa C - dan bahasa B dan BCPL yang mendahuluinya - bermaksud untuk mendefinisikan konstruksi dalam bahasa yang dikumpulkan seefisien mungkin ke dalam Assembly ... pada kenyataannya, mereka dipaksa oleh batasan dalam target perangkat keras. Seperti jawaban lain telah menunjukkan, ini melibatkan cabang (GOTO dan kontrol aliran lainnya di C), gerakan (tugas), operasi logis (& | ^), aritmatika dasar (menambah, mengurangi, menambah, mengurangi), dan pengalamatan memori (pointer ). Contoh yang bagus adalah operator pre- / post-increment dan decrement dalam C, yang seharusnya ditambahkan ke bahasa B oleh Ken Thompson secara khusus karena mereka mampu menerjemahkan langsung ke opcode tunggal setelah dikompilasi.
Inilah yang dimaksud penulis ketika mereka mengatakan "didukung langsung oleh sebagian besar komputer". Mereka tidak berarti bahwa bahasa lain yang terdapat jenis dan struktur yang tidak didukung langsung - mereka berarti bahwa dengan desain C konstruksi diterjemahkan paling langsung (kadang-kadang secara harfiah langsung) ke Majelis.
Hubungan dekat dengan Assembly yang mendasarinya, sambil tetap menyediakan semua elemen yang diperlukan untuk pemrograman terstruktur, inilah yang menyebabkan adopsi awal C, dan yang membuatnya tetap menjadi bahasa populer saat ini di lingkungan di mana efisiensi kode yang dikompilasi masih menjadi kuncinya.
Untuk artikel menarik tentang sejarah bahasa, lihat Perkembangan Bahasa C - Dennis Ritchie
sumber
Jawaban singkatnya adalah, sebagian besar konstruksi bahasa yang didukung oleh C juga didukung oleh mikroprosesor komputer target, oleh karena itu, kode C yang dikompilasi menerjemahkan dengan sangat baik dan efisien ke bahasa rakitan mikroprosesor, sehingga menghasilkan kode yang lebih kecil dan footprint yang lebih kecil.
Jawaban yang lebih panjang membutuhkan sedikit pengetahuan bahasa assembly. Di C, pernyataan seperti ini:
akan menerjemahkan sesuatu seperti ini dalam perakitan:
Bandingkan ini dengan sesuatu seperti C ++:
Kode bahasa assembly yang dihasilkan (bergantung pada seberapa besar MyClass ()), dapat menambahkan hingga ratusan baris bahasa assembly.
Tanpa benar-benar membuat program dalam bahasa assembly, C murni mungkin adalah kode "paling kurus" dan "paling ketat" tempat Anda dapat membuat program.
EDIT
Mengingat komentar pada jawaban saya, saya memutuskan untuk menjalankan tes, hanya untuk kewarasan saya sendiri. Saya membuat program bernama "test.c", yang terlihat seperti ini:
Saya menyusun ini ke perakitan menggunakan gcc. Saya menggunakan baris perintah berikut untuk mengkompilasinya:
Berikut adalah bahasa assembly yang dihasilkan:
Saya kemudian membuat file bernama "test.cpp" yang mendefinisikan kelas dan mengeluarkan hal yang sama seperti "test.c":
Saya menyusunnya dengan cara yang sama, menggunakan perintah ini:
Berikut adalah file assembly yang dihasilkan:
Seperti yang dapat Anda lihat dengan jelas, file assembly yang dihasilkan jauh lebih besar pada file C ++ daripada pada file C. Bahkan jika Anda memotong semua hal lainnya dan hanya membandingkan C "utama" dengan C ++ "utama", ada banyak hal tambahan.
sumber
MyClass myClass { 10 }
di C ++ sangat mungkin untuk dikompilasi ke perakitan yang persis sama. Kompiler C ++ modern telah menghilangkan penalti abstraksi. Dan sebagai hasilnya, mereka seringkali dapat mengalahkan kompiler C. Misalnya penalti abstraksi dalam Cqsort
nyata, tetapi C ++std::sort
tidak memiliki penalti abstraksi bahkan setelah pengoptimalan dasar.K&R berarti bahwa sebagian besar ekspresi C (arti teknis) dipetakan ke satu atau beberapa instruksi assembly, bukan panggilan fungsi ke pustaka dukungan. Pengecualian biasanya adalah pembagian integer pada arsitektur tanpa instruksi div perangkat keras, atau floating point pada mesin tanpa FPU.
Ada kutipannya:
( Ditemukan di sini . Saya pikir saya ingat variasi yang berbeda, seperti "kecepatan bahasa assembly dengan kenyamanan dan ekspresi bahasa assembly".)
long int biasanya memiliki lebar yang sama dengan register mesin asli.
Beberapa bahasa tingkat yang lebih tinggi menentukan lebar yang tepat dari tipe datanya, dan implementasi pada semua mesin harus bekerja dengan cara yang sama. Tapi bukan C.
Jika Anda ingin bekerja dengan int 128bit pada x86-64, atau dalam kasus umum BigInteger dengan ukuran arbitrer, Anda memerlukan pustaka fungsi untuk itu. Semua CPU sekarang menggunakan komplemen 2 sebagai representasi biner dari bilangan bulat negatif, tetapi bahkan itu tidak terjadi saat C dirancang. (Itulah mengapa beberapa hal yang akan memberikan hasil berbeda pada mesin non-pelengkap 2 secara teknis tidak ditentukan dalam standar C.)
C pointer ke data atau fungsi bekerja dengan cara yang sama seperti alamat assembly.
Jika Anda menginginkan referensi yang dihitung ulang, Anda harus melakukannya sendiri. Jika Anda menginginkan fungsi anggota virtual c ++ yang memanggil fungsi yang berbeda tergantung pada jenis objek yang dituju penunjuk Anda, kompilator C ++ harus menghasilkan lebih dari sekadar
call
instruksi dengan alamat tetap.String hanyalah array
Di luar fungsi perpustakaan, satu-satunya operasi string yang disediakan adalah membaca / menulis karakter. Tanpa concat, tanpa substring, tanpa pencarian. (String disimpan sebagai
'\0'
array nul-terminated ( ) dari 8bit integer, bukan pointer + length, jadi untuk mendapatkan substring Anda harus menulis nul ke dalam string aslinya.)CPU terkadang memiliki instruksi yang dirancang untuk digunakan oleh fungsi pencarian string, tetapi biasanya masih memproses satu byte per instruksi yang dieksekusi, dalam satu lingkaran. (atau dengan awalan rep x86. Mungkin jika C dirancang pada x86, pencarian atau perbandingan string akan menjadi operasi asli, bukan panggilan fungsi perpustakaan.)
Banyak jawaban lain memberikan contoh hal-hal yang tidak didukung secara native, seperti penanganan pengecualian, tabel hash, daftar. Filosofi desain K & R adalah alasan C tidak memiliki semua ini secara asli.
sumber
Bahasa assembly dari suatu proses umumnya berhubungan dengan jump (go to), statement, move statement, binary arthritis (XOR, NAND, AND OR, etc), memory field (atau address). Mengategorikan memori menjadi dua jenis, instruksi dan data. Itu tentang semua bahasa assembly adalah (saya yakin programmer assembly akan berpendapat ada yang lebih dari itu, tetapi intinya adalah ini secara umum). C sangat mirip dengan kesederhanaan ini.
C adalah merangkai apa itu aljabar menjadi aritmatika.
C merangkum dasar-dasar perakitan (bahasa prosesor). Mungkin pernyataan yang lebih benar daripada "Karena tipe data dan struktur kontrol yang disediakan oleh C didukung langsung oleh sebagian besar komputer"
sumber
Waspadalah terhadap perbandingan yang menyesatkan
sumber
Semua tipe data fundamental dan operasinya dalam bahasa C dapat diimplementasikan oleh satu atau beberapa instruksi bahasa mesin tanpa perulangan - mereka secara langsung didukung oleh (hampir setiap) CPU.
Beberapa tipe data populer dan operasinya memerlukan lusinan instruksi bahasa mesin, atau memerlukan iterasi dari beberapa runtime loop, atau keduanya.
Banyak bahasa memiliki sintaks singkat khusus untuk tipe seperti itu dan operasinya - menggunakan tipe data seperti itu di C biasanya membutuhkan pengetikan lebih banyak kode.
Jenis data, dan operasi tersebut meliputi:
Semua operasi ini memerlukan lusinan instruksi bahasa mesin atau memerlukan iterasi beberapa putaran runtime di hampir setiap prosesor.
Beberapa struktur kontrol populer yang juga memerlukan lusinan instruksi atau perulangan bahasa mesin meliputi:
Baik ditulis dalam C atau bahasa lain, ketika sebuah program memanipulasi tipe data tersebut, CPU pada akhirnya harus menjalankan instruksi apa pun yang diperlukan untuk memanipulasi tipe data tersebut. Instruksi tersebut sering kali terdapat dalam "perpustakaan". Setiap bahasa pemrograman, bahkan C, memiliki "run-time library" untuk setiap platform yang disertakan secara default di setiap executable.
Kebanyakan orang yang menulis kompiler meletakkan instruksi untuk memanipulasi semua tipe data yang "dibangun ke dalam bahasa" ke dalam pustaka run-time mereka. Karena C tidak memiliki apapun satu tipe data di atas dan operasi serta struktur kontrol yang dibangun ke dalam bahasa, tidak ada satupun yang disertakan dalam pustaka run-time C - yang membuat pustaka run-time C lebih kecil daripada run- pustaka waktu dari bahasa pemrograman lain yang memiliki lebih banyak hal di atas yang ada di dalam bahasa tersebut.
Ketika seorang programmer menginginkan sebuah program - dalam C atau bahasa lain pilihannya - untuk memanipulasi tipe data lain yang tidak "dibangun ke dalam bahasa", programmer tersebut biasanya memberi tahu kompiler untuk menyertakan pustaka tambahan dengan program itu, atau terkadang (untuk "menghindari ketergantungan") menulis implementasi lain dari operasi tersebut langsung dalam program.
sumber
Apa saja tipe data bawaan
C
? Mereka adalah hal-hal sepertiint
,char
,* int
,float
, array dll ... tipe data ini dipahami oleh CPU. CPU tahu bagaimana bekerja dengan array, bagaimana melakukan dereferensi pointer dan bagaimana melakukan aritmatika pada pointer, integer dan angka floating point.Tetapi ketika Anda beralih ke bahasa pemrograman tingkat yang lebih tinggi, Anda telah membangun tipe data abstrak dan konstruksi yang lebih kompleks. Sebagai contoh, lihat beragam kelas bawaan dalam bahasa pemrograman C ++. CPU tidak memahami kelas, objek, atau tipe data abstrak, sehingga waktu proses C ++ menjembatani kesenjangan antara CPU dan bahasa. Ini adalah contoh tipe data yang tidak didukung langsung oleh kebanyakan komputer.
sumber
Itu tergantung pada komputer. Pada PDP-11, tempat C ditemukan,
long
tidak didukung dengan baik (ada modul tambahan opsional yang dapat Anda beli yang mendukung beberapa, tetapi tidak semua, operasi 32-bit). Hal yang sama berlaku untuk berbagai tingkatan pada sistem 16-bit apa pun, termasuk PC IBM asli. Dan juga untuk operasi 64-bit pada mesin 32-bit atau dalam program 32-bit, meskipun bahasa C pada saat buku K&R tidak memiliki operasi 64-bit sama sekali. Dan tentu saja ada banyak sistem sepanjang tahun 80-an dan 90-an [termasuk 386 dan sekitar 486 prosesor], dan bahkan beberapa sistem tertanam saat ini, yang tidak secara langsung mendukung aritmatika floating point (float
ataudouble
).Untuk contoh yang lebih eksotis, beberapa arsitektur komputer hanya mendukung pointer "berorientasi kata" (menunjuk pada integer dua byte atau empat byte dalam memori), dan pointer byte (
char *
atauvoid *
) harus diimplementasikan dengan menambahkan bidang offset ekstra. Pertanyaan ini membahas beberapa detail tentang sistem semacam itu.Fungsi "run-time library" yang dirujuknya bukanlah yang akan Anda lihat di manual, tetapi fungsi seperti ini, dalam library waktu proses kompiler modern , yang digunakan untuk mengimplementasikan operasi tipe dasar yang tidak didukung oleh mesin . Perpustakaan runtime yang dirujuk oleh K&R sendiri dapat ditemukan di situs web The Unix Heritage Society - Anda dapat melihat fungsi seperti
ldiv
(berbeda dari fungsi C dengan nama yang sama, yang tidak ada pada saat itu) yang digunakan untuk mengimplementasikan pembagian Nilai 32-bit, yang tidak didukung PDP-11 bahkan dengan add-on, dancsv
(dancret
juga di csv.c) yang menyimpan dan memulihkan register di stack untuk mengelola panggilan dan pengembalian dari fungsi.Mereka mungkin juga mengacu pada pilihan mereka untuk tidak mendukung banyak tipe data yang tidak secara langsung didukung oleh mesin yang mendasarinya, tidak seperti bahasa kontemporer lainnya seperti FORTRAN, yang memiliki semantik array yang tidak memetakan juga ke dukungan penunjuk yang mendasari CPU seperti Array C. Fakta bahwa array C selalu diindeks nol dan selalu berukuran diketahui di semua peringkat tetapi yang pertama berarti bahwa tidak perlu menyimpan rentang indeks atau ukuran array, dan tidak perlu memiliki fungsi pustaka runtime untuk mengaksesnya - kompiler dapat dengan mudah melakukan hardcode aritmatika pointer yang diperlukan.
sumber
Pernyataan tersebut secara sederhana berarti bahwa data dan struktur kontrol di C berorientasi pada mesin.
Ada dua aspek yang perlu dipertimbangkan di sini. Salah satunya adalah bahwa bahasa C memiliki definisi (standar ISO) yang memungkinkan garis lintang dalam cara tipe data didefinisikan. Ini berarti implementasi bahasa C disesuaikan dengan mesin . Tipe data kompilator C cocok dengan apa yang tersedia di mesin yang ditargetkan kompilator, karena bahasa memiliki garis lintang untuk itu. Jika sebuah mesin memiliki ukuran kata yang tidak biasa, seperti 36 bit, maka jenisnya
int
ataulong
dapat dibuat untuk menyesuaikan dengan itu. Program yang mengasumsikanint
persis 32 bit akan rusak.Kedua, karena masalah portabilitas seperti itu, ada efek kedua. Di satu sisi, pernyataan dalam K&R telah menjadi semacam ramalan yang terpenuhi dengan sendirinya , atau mungkin sebaliknya. Artinya, pelaksana prosesor baru sadar akan kebutuhan yang tajam untuk mendukung kompiler C, dan mereka tahu bahwa terdapat banyak kode C yang mengasumsikan bahwa "setiap prosesor terlihat seperti 80386". Arsitektur dirancang dengan C dalam pikiran: dan tidak hanya C dalam pikiran, tetapi dengan kesalahpahaman umum tentang portabilitas C juga. Anda tidak bisa lagi memperkenalkan mesin dengan 9 bit byte atau apa pun untuk penggunaan tujuan umum lagi. Program yang mengasumsikan bahwa tipe
char
adalah lebar 8 bit yang tepat akan rusak. Hanya beberapa program yang ditulis oleh ahli portabilitas yang akan terus bekerja: sepertinya tidak cukup untuk menggabungkan sistem lengkap dengan toolchain, kernel, ruang pengguna, dan aplikasi yang berguna, dengan upaya yang wajar. Dengan kata lain, tipe C terlihat seperti apa yang tersedia dari perangkat keras karena perangkat keras dibuat agar terlihat seperti perangkat keras lain yang banyak program C nonportable telah ditulis.Tipe data tidak secara langsung didukung dalam banyak bahasa mesin: integer multi-presisi; daftar tertaut; tabel hash; string karakter.
Struktur kontrol tidak secara langsung didukung di sebagian besar bahasa mesin: kelanjutan kelas satu; coroutine / benang; generator; penanganan pengecualian.
Semua ini membutuhkan kode dukungan run-time yang cukup yang dibuat menggunakan berbagai instruksi tujuan umum, dan tipe data yang lebih mendasar.
C memiliki beberapa tipe data standar yang tidak didukung oleh beberapa mesin. Sejak C99, C memiliki bilangan kompleks. Mereka dibuat dari dua nilai floating-point dan dibuat untuk bekerja dengan rutinitas perpustakaan. Beberapa mesin tidak memiliki unit floating-point sama sekali.
Berkenaan dengan beberapa tipe data, tidak jelas. Jika mesin memiliki dukungan untuk pengalamatan memori menggunakan satu register sebagai alamat dasar, dan yang lainnya sebagai perpindahan berskala, apakah itu berarti bahwa array adalah tipe data yang didukung secara langsung?
Juga, berbicara tentang floating-point, ada standarisasi: IEEE 754 floating-point. Mengapa compiler C Anda memiliki a
double
yang sesuai dengan format floating-point yang didukung oleh prosesor bukan hanya karena keduanya dibuat untuk setuju, tetapi karena ada standar independen untuk representasi itu.sumber
Hal-hal seperti
Daftar Digunakan di hampir semua bahasa fungsional.
Pengecualian .
Array asosiatif (Maps) - termasuk dalam misalnya PHP dan Perl.
Pengumpulan sampah .
Tipe data / struktur kontrol termasuk dalam banyak bahasa, tetapi tidak didukung secara langsung oleh CPU.
sumber
Didukung secara langsung harus dipahami sebagai pemetaan secara efisien ke set instruksi prosesor.
Dukungan langsung untuk tipe integer adalah aturannya, kecuali untuk yang panjang (mungkin memerlukan rutinitas aritmatika yang diperpanjang) dan ukuran pendek (mungkin memerlukan masking).
Dukungan langsung untuk tipe floating-point memerlukan ketersediaan FPU.
Dukungan langsung untuk bidang bit luar biasa.
Struktur dan array memerlukan komputasi alamat, yang secara langsung didukung sampai batas tertentu.
Pointer selalu didukung secara langsung melalui pengalamatan tidak langsung.
goto / if / while / for / do secara langsung didukung oleh cabang tak bersyarat / bersyarat.
sakelar dapat langsung didukung ketika tabel lompat berlaku.
Panggilan fungsi secara langsung didukung melalui fitur stack.
sumber