Fungsi sebaris dalam C ++. Apa gunanya?

19

Menurut apa yang saya baca, kompiler tidak berkewajiban untuk mengganti fungsi panggilan fungsi inline dengan tubuhnya, tetapi akan melakukannya jika bisa. Ini membuat saya berpikir - mengapa kita memiliki kata inline jika itu masalahnya? Mengapa tidak membuat semua fungsi sebaris fungsi secara default dan membiarkan kompiler mengetahui apakah ia dapat menggantikan panggilan dengan badan fungsi atau tidak?

EpsilonVector
sumber
1
Di sini . Halaman 6
EpsilonVector

Jawaban:

40

inlineberasal dari C; itu bukan hal baru untuk C ++.

Ada kata kunci C ( registerdan inline) yang dirancang untuk memungkinkan programmer untuk membantu dalam optimasi kode. Ini umumnya diabaikan saat ini, karena kompiler dapat melakukan yang lebih baik dalam mendaftar tugas dan memutuskan kapan harus inline fungsi (pada kenyataannya, kompiler dapat inline atau tidak inline fungsi pada waktu yang berbeda). Pembuatan kode pada prosesor modern jauh lebih rumit daripada pada yang lebih deterministik umum ketika Ritchie menciptakan C.

Apa arti kata itu sekarang, dalam C ++, adalah bahwa ia dapat memiliki banyak definisi identik, dan perlu didefinisikan di setiap unit terjemahan yang menggunakannya. (Dengan kata lain, Anda perlu memastikan itu dapat digarisbawahi.) Anda dapat memiliki inlinefungsi di header tanpa masalah, dan fungsi anggota yang didefinisikan dalam definisi kelas secara otomatis efektif inline.

David Thornley
sumber
3
+1 untuk riwayat inline.
Tamara Wijsman
11
Saya cukup yakin bahwa inlineitu standar di C ++ pertama, meskipun sudah tersedia sebagai ekstensi vendor di C .... ya, sepertinya itu ditambahkan ke standar C di C99.
Ben Voigt
@ Ben Voigt: Anda benar. Saya pertama kali menemukannya di C.
David Thornley
5
Perlu diketahui juga bahwa tidak hanya histori yang salah, tetapi aturan untuk inlinedi C99 dan yang lebih baru berbeda dengan aturan inlinedi dalam C ++.
Alf P. Steinbach
27

Awalnya inlineadalah petunjuk yang sangat kuat bahwa panggilan ke fungsi harus diuraikan.

Tetapi satu-satunya efek dijamininline adalah untuk memungkinkan suatu fungsi untuk didefinisikan (secara efektif identik) dalam beberapa unit terjemahan, misalnya, bahwa Anda menempatkan definisi dalam file header.

Saat ini, beberapa kompiler sangat tertarik mengikuti petunjuk inlining, misalnya g ++. Dan beberapa kompiler menganggapnya kurang serius, misalnya Visual C ++. Tetapi semua harus mematuhi jaminan.

Sangat disayangkan bahwa kedua makna ini - petunjuk pengoptimalan dan apa yang kita sebut definisi dibuang tingkat tautan - berada dengan kata kunci yang sama, karena itu berarti bahwa Anda tidak dapat secara praktis memilikinya tanpa yang lain.

Sangat disayangkan bahwa inline(atau lebih baik, kata kunci terpisah tentang definisi yang dapat dibuang) ¹tidak dapat diterapkan pada data .

Kebutuhan akan data yang dapat dibuang pada level linker telah meningkat karena modul yang hanya menggunakan header menjadi lebih populer. Misalnya, banyak sub-pustaka Boost hanya header-saja.

Namun, untuk data Anda dapat menerapkan sedikit trik dengan templat. Tentukan dalam beberapa templat kelas, berikan typedefparameter templat dengan void(atau apa pun). Itu karena Aturan Satu Definisi membuat pengecualian khusus untuk template.

Catatan: variabel
¹ inlineakan didukung dalam C ++ 17 .

Alf P. Steinbach
sumber
1
+1 untuk informasi tambahan tentang inline.
Tamara Wijsman
1
" secara efektif identik " sebenarnya adalah kondisi yang sangat sulit, karena bahasa C ++ yang dirancang dengan sangat buruk berusaha sangat keras untuk membuat programmer yang kompeten dan berhati-hati menulis kode yang wajar tetapi tidak terdefinisi secara formal yang menggunakan objek statis - Saya ingin tahu berapa banyak anggota komite yang jatuh ke dalam perangkap itu sendiri
curiousguy
6

Mengapa tidak membuat semua fungsi sebaris secara default? Karena ini merupakan trade off teknik. Setidaknya ada dua jenis "optimasi": mempercepat program dan mengurangi ukuran (jejak memori) program. Inlining umumnya mempercepat. Ini menghilangkan overhead panggilan fungsi, menghindari mendorong lalu menarik parameter dari stack. Namun, itu juga membuat tapak memori program lebih besar, karena setiap pemanggilan fungsi sekarang harus diganti dengan kode lengkap fungsi. Untuk membuat segala sesuatunya menjadi lebih rumit, ingatlah bahwa toko CPU sering menggunakan potongan memori dalam cache pada CPU untuk akses ultra cepat. Jika Anda membuat gambar memori program cukup besar, program Anda tidak akan dapat menggunakan cache secara efisien, dan dalam kasus terburuk inlining sebenarnya dapat memperlambat program Anda.

Charles E. Grant
sumber
5
Tetapi kompiler dapat (dan tidak!) Mencari tahu ini jauh lebih baik daripada programmer pada umumnya. Jadi ini bukan argumen yang valid.
Konrad Rudolph
" setiap panggilan fungsi sekarang harus diganti dengan kode lengkap dari fungsi " Dan fungsi sebaris bukan fungsi di mana urutan panggilan dihilangkan dan di mana kode perakitan fungsi disalin di tempat. Fungsi inline dikompilasi di tempat, dengan membawa inline code tingkat tinggi inline. Itu memungkinkan kompiler untuk memperlakukan fungsi tubuh secara efektif sebagai makro bersih (makro bersih adalah makro yang tidak memiliki kebiasaan makro preprocessor C), dengan banyak optimisasi yang tersedia.
curiousguy
3

Untuk memahami “sebaris” Anda perlu memahami sejarah dan seperti apa kehidupan 20 (dan 30) tahun yang lalu.

Kami sedang menulis kode pada komputer yang memiliki sedikit memori, jadi tidak mungkin bagi kompiler untuk memproses semua kode yang membentuk sebuah program sekaligus. Kompiler juga sangat lambat, jadi Anda tidak ingin harus mengkompilasi ulang kode yang tidak berubah - mengambil alih 24 jam (pada komputer yang harganya lebih dari mobil kelas atas) untuk mengkompilasi ulang semua kode yang normal untuk beberapa proyek I bekerja pada.

Oleh karena itu setiap file kode dikompilasi secara terpisah menjadi file objek. Setiap file objek dimulai dengan daftar semua fungsi yang dikandungnya, bersama dengan "alamat" fungsi. File objek juga memiliki daftar semua fungsi yang dipanggil dalam file objek lain bersama dengan lokasi panggilan.

Seorang linker pertama-tama akan membaca semua file objek, dan menyusun daftar semua fungsi yang mereka ekspor, bersama dengan file yang ada di sana dan alamatnya. Kemudian akan membaca kembali semua file objek, mengeluarkannya ke file program, sambil memperbarui semua panggilan fungsi "eksternal" dengan alamat fungsi.

Linker tidak mengubah atau mengoptimalkan kode mesin yang dihasilkan oleh kompiler dengan cara apa pun selain untuk memperbaiki referensi untuk panggilan fungsi eksternal. Linker adalah bagian dari sistem operasi dan mendahului sebagian besar kompiler. Ketika orang menulis kompiler baru, mereka memerlukannya untuk bekerja dengan penghubung saat ini, dan untuk dapat menautkan ke file objek saat ini, jika tidak, panggilan sistem tidak dapat dibuat.

Kompiler hanya pernah melihat kode dalam file ".c" atau ".cpp" yang dikompilasi bersama dengan semua file header yang disertakan. Jadi itu tidak dapat membuat optimasi berdasarkan kode di file ".c" atau ".cpp" lainnya.

Kata kunci "inline" memungkinkan tubuh fungsi (metode) untuk didefinisikan dalam file header, sehingga memungkinkan kompiler untuk menggunakan kode fungsi sambil mengkompilasi kode yang memanggilnya. Misalnya, Anda memiliki kelas koleksi yang ditentukan dalam file .cpp anther, kelas ini akan memiliki metode "isEmpty", yang berisi satu baris kode, akan ada percepatan besar dari program yang dihasilkan jika alih-alih panggilan ke fungsi , panggilan fungsi diganti dengan satu baris ini.

Kata kunci "inline" dilihat pada saat itu sebagai cara "murah dan mudah" untuk memungkinkan enkapsulasi data sambil menghindari biaya pemanggilan fungsi, tanpa itu banyak programmer akan hanya mengakses bidang pribadi objek. (Makro di mana cara yang jauh lebih buruk "inlining" kode yang mana umum pada saat itu.)

“Linker” akhir-akhir ini melakukan banyak optimasi kode dan cenderung ditulis oleh beberapa tim sebagai kompiler. Compiler sering hanya memeriksa kode yang benar dan "kompres" itu, meninggalkan sebagian besar tugas pembuatan kode mesin ke linker.

Ian
sumber
2

Mari kita lihat apa yang dikatakan standar (bagian penting yang dicetak tebal):

2. Deklarasi fungsi dengan specifier sebaris menyatakan fungsi sebaris. Penspesifikasi sebaris menunjukkan pada implementasi bahwa substitusi sebaris dari fungsi tubuh pada titik panggilan lebih disukai daripada mekanisme panggilan fungsi yang biasa. Implementasi tidak diperlukan untuk melakukan penggantian inline ini di titik panggilan; namun, bahkan jika penggantian inline ini dihilangkan, aturan lain untuk fungsi inline tetap harus dihormati.

- Standar C ++, ISO / IEC 14882: 2003 , 7.1.2 Penentu Fungsi [dcl.fct.spec]

Jadi, jika Anda ingin memastikan, Anda harus membaca dokumentasi kompiler Anda.

Memasukkan semuanya adalah ide yang buruk, karena mungkin menghasilkan banyak kode mesin duplikat ...

Jadi, Anda harus tahu:

Tidak ada jawaban sederhana: Anda harus bermain dengannya untuk melihat apa yang terbaik. Jangan tidak puas jawaban sederhana seperti, "Jangan pernah menggunakan inlinefungsi" atau "Selalu gunakan inlinefungsi" atau "Gunakan inlinefungsi jika dan hanya jika fungsi kurang dari N baris kode." Aturan satu ukuran untuk semua ini mungkin mudah ditulis, tetapi akan menghasilkan hasil yang kurang optimal.

- C ++ FAQ, Fungsi Inline , 9.3 Apakah inlinefungsi meningkatkan kinerja?

Tamara Wijsman
sumber
1
Apa yang Anda katakan itu benar, tetapi tidak relevan karena sebagai programmer itu bukan keputusan Anda apakah akan inline atau tidak. Anda memasukkan kata kunci, Anda mungkin atau mungkin tidak mendapatkan fungsi Anda sebaris. Anda tidak memasukkan kata kunci, Anda masih mungkin atau mungkin tidak membuatnya inline. Tidak ada gunanya memiliki aturan APAPUN ketika Anda meletakkan kata kunci, apa yang membuat kompiler yang membuat keputusan.
Kate Gregory
Bagaimana ini tidak relevan jika dikatakan persis sama dengan Anda? Tidak ada jawaban sederhana .
Tamara Wijsman
Itu tidak relevan karena tidak menjawab pertanyaan OP. Dia tidak bertanya "apa yang dilakukan inline", dia bertanya "apa gunanya". Dan jawaban Anda hanya mengulangi apa yang sudah kita ketahui tentang inline.
Davor Ždralo
@ Nikmat: "Apa gunanya?" tidak mematuhi FAQ, jadi saya menjawab pertanyaan yang diajukan dalam pertanyaan. Saya mengatakan titik inlinedengan mengulangi pengetahuan sebagai OP tampaknya melewatkan bagian jadi yang merupakan alasan utama mengapa dia tidak mendapatkan intinya. Agar seseorang mendapatkan poin, dia perlu memiliki pemahaman tentang apa yang ada di bawah poin itu ...
Tamara Wijsman
Kenapa tidak menempel pada FAQ? Standar menjelaskan sintaks dan semantik kata kunci inline, dan pertanyaan OPs adalah apa tujuannya, kapan harus digunakan, masalah apa yang harus dipecahkan? Saya tidak melihat bagaimana itu tidak mematuhi FAQ.
Davor Ždralo
1

Biarkan saya memberi Anda alasan bagus untuk menggunakan kata kunci inline.

Pada sistem yang disematkan, seperti printer tiket atau sistem kecil yang serupa. Prosesor sangat terbatas dan pemanggilan fungsi (menyiapkan parameter fungsi pada stack, panggilan, mengambil params dari stack, dan mengembalikan jawaban, dll.) Dapat mengambil beberapa ms untuk dijalankan, di samping fungsi itu sendiri.

Katakanlah waktu panggilan adalah sekitar 60 ms (hanya untuk menelepon, bukan fungsi sebenarnya) dan Anda harus melakukan 50 iterasi (loop atau panggilan berulang di pohon).

Waktu untuk hanya bergerak maju dan mundur dari pemanggilan fungsi itu akan membutuhkan 60 * 50 = 3000 (3 detik).

Jika Anda memiliki memori, Anda pasti akan melakukan inline untuk menghemat 3 detik.

Jadi inline pada dasarnya digunakan ketika Anda membutuhkan kecepatan eksekusi. Dalam beberapa proyek saya terlibat, waktu panggilan lebih lama dari waktu eksekusi, situasi klasik kapan harus menggunakan inline.

Max Kielland
sumber
Eh apa? Menempatkan inline dalam kode Anda tidak berarti bahwa kompiler akan benar-benar inline itu, dan tidak meletakkannya di sana juga tidak berarti bahwa ia tidak akan melakukannya. Itu hanya petunjuk, yang sebagian besar diabaikan oleh penyusun todays. Jika kompiler merasa berguna untuk inline, maka ia akan, jika tidak, ia tidak akan melakukannya. Anda tidak memiliki kendali nyata di sana. OP sudah mengetahui hal ini, dan bertanya, saya kutip: "Apa gunanya?". Siapa yang memberi +1 pada ini? Keduanya menyesatkan (menyiratkan bahwa kata kunci inline memberlakukan inlining) dan tidak relevan dengan pertanyaan.
Davor Ždralo
2
@Davor Ždralo Tidak perlu kasar. Saya menafsirkan pertanyaan sebagai MENGAPA saya harus menggunakan inline? Maksud saya adalah untuk menunjukkan bahwa Anda bisa mendapatkan kode lebih cepat dengan biaya memori. Setiap kompiler dapat menangani kata kunci inline yang berbeda, jadi Anda perlu memeriksa dokumentasinya, yang tidak saya sebutkan. Karena Anda tidak selalu dapat membeli inline overhead memori tambahan, penting untuk "membimbing" kapan menggunakannya. Saya juga tidak mengatakan bahwa inline ditegakkan. Seseorang menganggap jawaban ini berguna baginya dan memilih +1, harap hormati bahwa orang lain mungkin memiliki tingkat pengalaman lain dan menemukan sesuatu yang sepele berguna.
Max Kielland