Apakah "sebaris" tanpa "statis" atau "eksternal" pernah berguna di C99?

96

Saat saya mencoba membuat kode ini

inline void f() {}

int main()
{
    f();
}

menggunakan baris perintah

gcc -std=c99 -o a a.c

Saya mendapatkan kesalahan penaut (referensi tidak ditentukan ke f). Kesalahan hilang jika saya menggunakan static inlineatau extern inlinebukan hanya inline, atau jika saya mengkompilasi dengan -O(jadi fungsinya sebenarnya sebaris).

Perilaku ini tampaknya didefinisikan dalam paragraf 6.7.4 (6) standar C99:

Jika semua deklarasi cakupan file untuk suatu fungsi dalam unit terjemahan menyertakan inlinepenentu fungsi tanpa extern, maka definisi dalam unit terjemahan tersebut adalah definisi sebaris. Definisi sebaris tidak memberikan definisi eksternal untuk fungsi tersebut, dan tidak melarang definisi eksternal di unit terjemahan lain. Definisi sebaris memberikan alternatif untuk definisi eksternal, yang dapat digunakan penerjemah untuk mengimplementasikan panggilan apa pun ke fungsi dalam unit terjemahan yang sama. Tidak ditentukan apakah panggilan ke fungsi menggunakan definisi sebaris atau definisi eksternal.

Jika saya memahami semua ini dengan benar, unit kompilasi dengan fungsi yang ditentukan inlineseperti pada contoh di atas hanya mengkompilasi secara konsisten jika ada juga fungsi eksternal dengan nama yang sama, dan saya tidak pernah tahu apakah fungsi saya sendiri atau fungsi eksternal dipanggil.

Bukankah perilaku ini benar-benar gila? Apakah pernah berguna untuk mendefinisikan fungsi inlinetanpa staticatau externdi C99? Apakah saya melewatkan sesuatu?

Ringkasan jawaban

Tentu saja saya melewatkan sesuatu, dan perilakunya tidak konyol. :)

Seperti yang dijelaskan Nemo , idenya adalah untuk meletakkan definisi fungsi

inline void f() {}

di file header dan hanya deklarasi

extern inline void f();

di file .c yang sesuai. Hanya externdeklarasi yang memicu pembuatan kode biner yang terlihat secara eksternal. Dan memang tidak ada gunanya inlinedalam file .c - ini hanya berguna di header.

Seperti yang dijelaskan oleh alasan komite C99 yang dikutip dalam jawaban Jonathan , inlinesemua tentang pengoptimalan compiler yang memerlukan definisi suatu fungsi agar terlihat di lokasi panggilan. Ini hanya dapat dicapai dengan meletakkan definisi di header, dan tentu saja definisi di header tidak boleh mengeluarkan kode setiap kali dilihat oleh kompiler. Tetapi karena kompilator tidak dipaksa untuk benar-benar menyebariskan suatu fungsi, definisi eksternal harus ada di suatu tempat.

Sven Marnach
sumber
duplikat garis batas stackoverflow.com/questions/5369888/…
Earlz
@Earlz: Terima kasih untuk tautannya. Pertanyaan saya adalah tentang alasan di balik paragraf standar yang dikutip, dan jika ada kasus penggunaan untuk inlinetanpa staticdan extern, meskipun. Sayangnya, tidak satu pun dari masalah ini tercakup dalam pertanyaan itu.
Sven Marnach
@ Nemo: Saya membaca pertanyaan itu dan jawabannya sebelum saya memposting milik saya. Sekali lagi, saya tidak bertanya "bagaimana perilakunya?" melainkan "apa ide di balik perilaku ini"? Saya cukup yakin saya melewatkan sesuatu di sini.
Sven Marnach
1
ya, itulah mengapa saya mengatakan "batas". Saya pribadi berpikir ini mengajukan pertanyaan yang sama sekali berbeda
Earlz

Jawaban:

40

Sebenarnya jawaban luar biasa ini juga menjawab pertanyaan Anda, saya pikir:

Apa yang dilakukan extern inline?

Idenya adalah bahwa "inline" dapat digunakan dalam file header, dan kemudian "extern inline" dalam file .c. "extern inline" adalah cara Anda menginstruksikan kompilator file objek mana yang harus berisi kode yang dihasilkan (terlihat secara eksternal).

[perbarui, untuk memperinci]

Saya rasa tidak ada gunanya "inline" (tanpa "static" atau "extern") dalam file .c. Namun dalam file header, hal ini masuk akal, dan memerlukan deklarasi "extern inline" yang sesuai di beberapa file .c untuk benar-benar menghasilkan kode yang berdiri sendiri.

Nemo
sumber
1
Terima kasih, saya mulai mengerti!
Sven Marnach
8
Ya, saya sendiri tidak pernah mengerti sampai sekarang, jadi terima kasih sudah bertanya :-). Aneh karena definisi "inline" non-ekstern berada di header (tetapi tidak selalu menghasilkan kode sama sekali), sedangkan deklarasi "inline extern" masuk dalam file .c dan sebenarnya menyebabkan kode menjadi dihasilkan.
Nemo
3
Apakah ini berarti saya menulis inline void f() {}di header dan extern inline void f();di file .c? Jadi definisi fungsi sebenarnya berada di header dan file .c berisi deklarasi belaka dalam kasus ini, dalam pembalikan urutan biasa?
Sven Marnach
1
@endolith: Saya tidak mengerti pertanyaan Anda. Kami berdiskusi inline tanpa static atau extern. Tentu saja static inlinebaik-baik saja, tetapi pertanyaan dan jawaban ini bukanlah tentang itu.
Nemo
2
@MatthieuMoy Anda perlu menggunakan, -std=c99bukan -std=gnu89.
a3f
27

Dari standar (ISO / IEC 9899: 1999) itu sendiri:

Lampiran J.2 Perilaku Tidak Terdefinisi

  • ...
  • Fungsi dengan tautan eksternal dideklarasikan dengan penentu inlinefungsi, tetapi tidak juga ditentukan dalam unit terjemahan yang sama (6.7.4).
  • ...

Komite C99 menulis Rationale , dan dikatakan:

6.7.4 Penentu fungsi

Fitur baru C99: Kata inlinekunci, diadaptasi dari C ++, adalah penentu fungsi yang hanya dapat digunakan dalam deklarasi fungsi. Berguna untuk pengoptimalan program yang memerlukan definisi fungsi agar terlihat di lokasi panggilan. (Perhatikan bahwa Standar tidak mencoba menentukan sifat pengoptimalan ini.)

Visibilitas dijamin jika fungsi tersebut memiliki hubungan internal, atau jika memiliki hubungan eksternal dan panggilan berada dalam unit terjemahan yang sama dengan definisi eksternal. Dalam kasus ini, keberadaan inlinekata kunci dalam deklarasi atau definisi fungsi tidak berpengaruh selain menunjukkan preferensi bahwa panggilan fungsi itu harus dioptimalkan dalam preferensi untuk panggilan fungsi lain yang dideklarasikan tanpa inlinekata kunci.

Visibilitas adalah masalah untuk pemanggilan suatu fungsi dengan linkage eksternal di mana panggilan tersebut berada dalam unit terjemahan yang berbeda dari definisi fungsi. Dalam kasus ini, inlinekata kunci memungkinkan unit terjemahan yang berisi panggilan untuk juga berisi definisi fungsi lokal atau sebaris.

Sebuah program dapat berisi unit terjemahan dengan definisi eksternal, unit terjemahan dengan definisi sebaris, dan unit terjemahan dengan deklarasi tetapi tidak ada definisi untuk suatu fungsi. Panggilan di unit terjemahan terakhir akan menggunakan definisi eksternal seperti biasa.

Definisi sebaris dari suatu fungsi dianggap sebagai definisi yang berbeda dari definisi eksternal. Jika panggilan ke beberapa fungsi funcdengan tautan eksternal terjadi di mana definisi sebaris terlihat, perilakunya sama seperti jika panggilan dilakukan ke fungsi lain, katakanlah __func, dengan tautan internal. Program yang sesuai tidak harus bergantung pada fungsi mana yang dipanggil. Ini adalah model sebaris di Standar.

Program yang sesuai tidak boleh bergantung pada implementasi menggunakan definisi inline, juga tidak boleh bergantung pada implementasi yang menggunakan definisi eksternal. Alamat suatu fungsi selalu merupakan alamat yang sesuai dengan definisi eksternal, tetapi bila alamat ini digunakan untuk memanggil fungsi tersebut, definisi sebaris dapat digunakan. Oleh karena itu, contoh berikut mungkin tidak berfungsi seperti yang diharapkan.

inline const char *saddr(void)
{
    static const char name[] = "saddr";
    return name;
}
int compare_name(void)
{
    return saddr() == saddr(); // unspecified behavior
}

Karena implementasi mungkin menggunakan definisi inline untuk salah satu panggilan ke saddrdan menggunakan definisi eksternal untuk panggilan lainnya, operasi kesetaraan tidak dijamin untuk mengevaluasi ke 1 (true). Ini menunjukkan bahwa objek statis yang didefinisikan dalam definisi sebaris berbeda dari objek yang sesuai dalam definisi eksternal. Hal ini memotivasi kendala bahkan untuk mendefinisikan non- constobjek jenis ini.

Inlining ditambahkan ke Standar sedemikian rupa sehingga dapat diimplementasikan dengan teknologi linker yang ada, dan subset dari C99 inlining kompatibel dengan C ++. Ini dicapai dengan mensyaratkan bahwa tepat satu unit terjemahan yang berisi definisi fungsi sebaris ditetapkan sebagai unit yang memberikan definisi eksternal untuk fungsi tersebut. Karena spesifikasi tersebut hanya terdiri dari deklarasi yang tidak memiliki inlinekata kunci, atau berisi keduanya inlinedan extern, itu juga akan diterima oleh penerjemah C ++.

Menyebariskan di C99 memang memperluas spesifikasi C ++ dalam dua cara. Pertama, jika sebuah fungsi dideklarasikan inlinedalam satu unit terjemahan, itu tidak perlu dideklarasikan inlinedi setiap unit terjemahan lainnya. Hal ini memungkinkan, misalnya, fungsi pustaka yang menjadi sebaris di dalam pustaka tetapi hanya tersedia melalui definisi eksternal di tempat lain. Alternatif penggunaan fungsi pembungkus untuk fungsi eksternal membutuhkan nama tambahan; dan juga dapat berdampak buruk pada kinerja jika penerjemah tidak benar-benar melakukan substitusi sebaris.

Kedua, persyaratan bahwa semua definisi fungsi inline menjadi "persis sama" diganti dengan persyaratan bahwa perilaku program tidak boleh bergantung pada apakah panggilan diimplementasikan dengan definisi inline yang terlihat, atau definisi eksternal, dari a fungsi. Hal ini memungkinkan definisi sebaris dikhususkan untuk digunakan dalam unit terjemahan tertentu. Misalnya, definisi eksternal dari fungsi pustaka mungkin menyertakan beberapa validasi argumen yang tidak diperlukan untuk panggilan yang dibuat dari fungsi lain di pustaka yang sama. Ekstensi ini memang menawarkan beberapa keuntungan; dan pemrogram yang peduli tentang kompatibilitas dapat dengan mudah mematuhi aturan C ++ yang lebih ketat.

Catatan bahwa itu tidak yang sesuai untuk implementasi untuk memberikan definisi inline fungsi perpustakaan standar dalam header standar karena ini dapat mematahkan beberapa kode warisan yang redeclares standar fungsi perpustakaan setelah termasuk header mereka. Kata inlinekunci hanya dimaksudkan untuk menyediakan cara portabel kepada pengguna untuk menyarankan sebaris fungsi. Karena header standar tidak perlu portabel, implementasi memiliki opsi lain di sepanjang baris:

#define abs(x) __builtin_abs(x)

atau mekanisme non-portabel lainnya untuk menyebariskan fungsi pustaka standar.

Jonathan Leffler
sumber
Terima kasih, ini sangat detail - dan hampir sama terbaca seperti standar itu sendiri. :-) Saya tahu saya pasti melewatkan sesuatu. Bisakah Anda memberikan referensi dari mana Anda mendapatkan ini?
Sven Marnach
Baru saja terlintas dalam pikiran saya bahwa Anda dapat menggunakan Google untuk menemukan tautan: open-std.org/jtc1/sc22/wg14/www/C99RationaleV5.10.pdf
Sven Marnach
@ Sven: Saya meminjam URL dari pencarian Anda dan memasukkannya ke dalam jawabannya. Dokumen yang saya gunakan adalah salinan dari alasan yang saya simpan sebelumnya (pada tahun 2005), tetapi juga V5.10.
Jonathan Leffler
4
Terima kasih lagi. Wawasan paling berharga yang saya dapat dari jawabannya adalah bahwa ada dokumen yang berisi dasar pemikiran standar C99 (dan mungkin ada dokumen untuk standar lain juga). Sementara jawaban Nemo memberi saya ikan, yang ini mengajari saya memancing.
Sven Marnach
0

> Saya mendapatkan kesalahan penaut (referensi tidak ditentukan ke f)

Bekerja di sini: Linux x86-64, GCC 4.1.2. Mungkin ada bug di kompiler Anda; Saya tidak melihat apa pun di paragraf yang dikutip dari standar yang melarang program yang diberikan. Perhatikan penggunaan if daripada iff .

Definisi sebaris memberikan alternatif untuk definisi eksternal , yang dapat digunakan penerjemah untuk mengimplementasikan panggilan apa pun ke fungsi dalam unit terjemahan yang sama.

Jadi, jika Anda mengetahui perilaku fungsi fdan ingin memanggilnya berulang kali, Anda dapat menyalin-tempel definisinya ke dalam modul untuk mencegah pemanggilan fungsi; atau , Anda dapat memberikan definisi yang, untuk tujuan modul saat ini, adalah setara (tetapi melewatkan validasi input, atau pengoptimalan apa pun yang dapat Anda bayangkan). Penulis kompilator, bagaimanapun, memiliki opsi untuk mengoptimalkan ukuran program.

Fred Foo
sumber
2
Standar menyatakan bahwa kompilator dapat selalu berasumsi bahwa ada fungsi eksternal dengan nama yang sama dan memanggilnya alih-alih versi sebaris, jadi menurut saya itu bukan bug kompilator. Saya mencoba dengan gcc 4.3, 4.4 dan 4.5, semuanya memberikan kesalahan linker.
Sven Marnach