Pengaruh kata kunci eksternal pada fungsi C.


172

Di C, saya tidak melihat adanya pengaruh externkata kunci yang digunakan sebelum deklarasi fungsi. Pada awalnya, saya berpikir bahwa ketika mendefinisikan extern int f();dalam satu file memaksa Anda untuk mengimplementasikannya di luar ruang lingkup file. Namun saya menemukan bahwa keduanya:

extern int f();
int f() {return 0;}

dan

extern int f() {return 0;}

kompilasi saja, tanpa peringatan dari gcc. Saya menggunakan gcc -Wall -ansi; bahkan tidak mau menerima //komentar.

Apakah ada efek untuk menggunakan extern sebelum definisi fungsi ? Atau itu hanya kata kunci opsional tanpa efek samping untuk fungsi.

Dalam kasus terakhir saya tidak mengerti mengapa perancang standar memilih untuk mengotori tata bahasa dengan kata kunci berlebihan.

EDIT: Untuk memperjelas, saya tahu ada penggunaan untuk externvariabel, tapi aku hanya bertanya tentang externdi fungsi .


Menurut beberapa penelitian yang saya lakukan ketika mencoba menggunakan ini untuk beberapa tujuan templating gila, eksternal tidak didukung dalam bentuk yang dimaksudkan oleh sebagian besar kompiler, dan jadi tidak benar-benar melakukan, well, apapun.
Ed James

4
Tidak selalu berlebihan, lihat jawaban saya. Setiap kali Anda perlu membagikan sesuatu di antara modul-modul yang TIDAK Anda inginkan di header publik, itu sangat berguna. Namun, 'externing' setiap fungsi tunggal dalam header publik (dengan kompiler modern) memiliki sedikit manfaat atau tidak, karena mereka dapat mengetahuinya sendiri.
Tim Post

@ Id .. jika volatile int foo adalah global di foo.c, dan bar.c membutuhkannya, bar.c harus mendeklarasikannya sebagai extern. Itu memang memiliki kelebihan. Selain itu, Anda mungkin perlu berbagi beberapa fungsi yang TIDAK ingin Anda tampilkan di header publik.
Tim Post


2
@ Larry Jika sama sekali, pertanyaan lain adalah duplikat dari yang ini. 2009 vs 2012
Elazar Leibovich

Jawaban:


138

Kami memiliki dua file, foo.c dan bar.c.

Ini foo.c

#include <stdio.h>

volatile unsigned int stop_now = 0;
extern void bar_function(void);

int main(void)
{
  while (1) {
     bar_function();
     stop_now = 1;
  }
  return 0;
}

Sekarang, ini bar.c

#include <stdio.h>

extern volatile unsigned int stop_now;

void bar_function(void)
{
   while (! stop_now) {
      printf("Hello, world!\n");
      sleep(30);
   }
}

Seperti yang Anda lihat, kami tidak memiliki tajuk bersama antara foo.c dan bar.c, namun bar.c membutuhkan sesuatu yang dideklarasikan di foo.c ketika terhubung, dan foo.c membutuhkan fungsi dari bar.c ketika terhubung.

Dengan menggunakan 'extern', Anda memberi tahu kompiler bahwa apa pun yang mengikutinya akan ditemukan (non-statis) pada waktu tautan; jangan mencadangkan apa pun untuknya di pass saat ini karena akan ditemui nanti. Fungsi dan variabel diperlakukan sama dalam hal ini.

Ini sangat berguna jika Anda perlu berbagi beberapa global antar modul dan tidak ingin meletakkan / menginisialisasi dalam header.

Secara teknis, setiap fungsi dalam tajuk publik pustaka adalah 'eksternal', namun memberi labelnya sangat sedikit atau tidak bermanfaat, tergantung pada kompilernya. Kebanyakan kompiler dapat mengetahuinya sendiri. Seperti yang Anda lihat, fungsi-fungsi tersebut sebenarnya didefinisikan di tempat lain.

Pada contoh di atas, main () akan mencetak hello world hanya sekali, tetapi terus memasukkan bar_function (). Perhatikan juga, bar_function () tidak akan kembali dalam contoh ini (karena ini hanya contoh sederhana). Bayangkan saja stop_now sedang dimodifikasi ketika sinyal diservis (karenanya, volatile) jika ini tampaknya tidak cukup praktis.

Eksternal sangat berguna untuk hal-hal seperti penangan sinyal, mutex yang tidak ingin Anda taruh di header atau struktur, dll. Kebanyakan kompiler akan mengoptimalkan untuk memastikan bahwa mereka tidak menyimpan memori untuk objek eksternal, karena mereka tahu mereka akan menyimpannya dalam modul di mana objek didefinisikan. Namun, sekali lagi, ada gunanya menspesifikasikannya dengan kompiler modern saat membuat prototipe fungsi publik.

Semoga itu bisa membantu :)


56
Kode Anda akan dikompilasi dengan baik tanpa eksternal sebelum fungsi bar_.
Elazar Leibovich

2
@Tim: Maka Anda tidak memiliki hak meragukan untuk bekerja dengan kode yang saya gunakan. Itu bisa terjadi. Terkadang tajuk juga berisi definisi fungsi statis. Itu jelek, dan tidak perlu 99,99% dari waktu (saya bisa pergi dengan pesanan atau dua atau besarnya, melebih-lebihkan seberapa sering diperlukan). Biasanya terjadi ketika orang salah mengerti bahwa header hanya diperlukan ketika file sumber lain akan menggunakan informasi; header (ab) digunakan untuk menyimpan informasi deklarasi untuk satu file sumber dan tidak ada file lain yang diharapkan untuk memasukkannya. Kadang-kadang, itu terjadi karena alasan yang lebih beragam.
Jonathan Leffler

2
@ Jonathan Leffler - Memang meragukan! Saya telah mewarisi beberapa kode yang agak samar sebelumnya, tapi saya bisa jujur ​​mengatakan saya belum pernah melihat seseorang meletakkan deklarasi statis di header. Kedengarannya seperti Anda memiliki pekerjaan yang agak menyenangkan dan menarik :)
Tim Post

1
Kerugian dari 'prototipe fungsi tidak di header' adalah bahwa Anda tidak mendapatkan pemeriksaan independen otomatis dari konsistensi antara definisi fungsi di bar.cdan deklarasi di foo.c. Jika fungsi dideklarasikan dalam foo.h dan kedua file termasuk foo.h, maka header memberlakukan konsistensi antara kedua file sumber. Tanpa itu, jika definisi bar_functiondalam bar.cperubahan tetapi deklarasi di foo.ctidak diubah, maka ada yang salah pada saat run-time; kompiler tidak dapat menemukan masalah. Dengan header yang digunakan dengan benar, kompiler melihat masalah.
Jonathan Leffler

1
extern pada fungsi deklarasi berlebihan seperti 'int' di 'unsigned int'. Ini praktik yang baik untuk menggunakan 'extern' ketika prototipe BUKAN deklarasi maju ... Tapi itu benar-benar harus hidup dalam header tanpa 'extern' kecuali tugasnya adalah kasus tepi. stackoverflow.com/questions/10137037/...

82

Sejauh yang saya ingat standarnya, semua deklarasi fungsi dianggap sebagai "extern" secara default, jadi tidak perlu menentukannya secara eksplisit.

Itu tidak membuat kata kunci ini tidak berguna karena juga dapat digunakan dengan variabel (dan jika demikian - itu satu-satunya solusi untuk menyelesaikan masalah keterkaitan). Tetapi dengan fungsi - ya, itu opsional.


21
Kemudian sebagai perancang standar saya akan melarang menggunakan eksternal dengan fungsi, karena hanya menambah kebisingan ke tata bahasa.
Elazar Leibovich

3
Kompatibilitas mundur bisa menyusahkan.
MathuSum Mut

1
@ElazarLeibovich Sebenarnya, dalam kasus ini, pelarangan itu adalah apa yang akan menambahkan suara untuk tata bahasa.
Lightness Races di Orbit

1
Betapa membatasi kata kunci menambah kebisingan di luar saya, tapi saya kira itu masalah selera.
Elazar Leibovich

Sangat berguna untuk memungkinkan penggunaan "extern" untuk fungsi, karena ini menunjukkan kepada programmer lain bahwa fungsi didefinisikan dalam file lain, tidak dalam file saat ini dan juga tidak dinyatakan dalam salah satu header yang disertakan.
DimP

23

Anda perlu membedakan antara dua konsep terpisah: definisi fungsi dan pernyataan simbol. "extern" adalah pengubah tautan, petunjuk ke kompiler tentang di mana simbol yang dirujuk sesudahnya didefinisikan (petunjuknya adalah, "tidak di sini").

Jika saya menulis

extern int i;

dalam cakupan file (di luar blok fungsi) dalam file C, maka Anda mengatakan "variabel dapat didefinisikan di tempat lain".

extern int f() {return 0;}

adalah deklarasi fungsi f dan definisi fungsi f. Definisi dalam hal ini mengesampingkan eksternal.

extern int f();
int f() {return 0;}

pertama adalah deklarasi, diikuti oleh definisi.

Penggunaan externsalah jika Anda ingin mendeklarasikan dan sekaligus menentukan variabel cakupan file. Sebagai contoh,

extern int i = 4;

akan memberikan kesalahan atau peringatan, tergantung pada kompiler.

Penggunaan externberguna jika Anda secara eksplisit ingin menghindari definisi variabel.

Biarkan saya jelaskan:

Katakanlah file ac berisi:

#include "a.h"

int i = 2;

int f() { i++; return i;}

File ah meliputi:

extern int i;
int f(void);

dan file bc berisi:

#include <stdio.h>
#include "a.h"

int main(void){
    printf("%d\n", f());
    return 0;
}

Extern di header berguna, karena memberitahu compiler selama fase tautan, "ini adalah deklarasi, dan bukan definisi". Jika saya menghapus baris dalam ac yang mendefinisikan i, mengalokasikan ruang untuknya dan memberikan nilai padanya, program harus gagal dikompilasi dengan referensi yang tidak ditentukan. Ini memberi tahu pengembang bahwa ia telah merujuk ke variabel, tetapi belum mendefinisikannya. Jika di sisi lain, saya menghilangkan kata kunci "extern", dan menghapus int i = 2baris, program masih mengkompilasi - saya akan didefinisikan dengan nilai default 0.

Variabel ruang lingkup file secara implisit didefinisikan dengan nilai default 0 atau NULL jika Anda tidak secara eksplisit memberikan nilai padanya - tidak seperti variabel blok-lingkup yang Anda nyatakan di bagian atas fungsi. Kata kunci eksternal menghindari definisi tersirat ini, dan dengan demikian membantu menghindari kesalahan.

Untuk fungsi, dalam deklarasi fungsi, kata kunci memang berlebihan. Deklarasi fungsi tidak memiliki definisi implisit.


Apakah Anda bermaksud menghapus int i = 2baris pada paragraf -3? Dan apakah benar untuk menyatakan, melihat int i;, kompiler akan mengalokasikan memori untuk variabel itu, tetapi melihat extern int i;, kompiler TIDAK akan mengalokasikan memori tetapi mencari variabel di tempat lain?
Api Beku

Sebenarnya jika Anda menghilangkan kata kunci "extern" program tidak akan mengkompilasi karena redefinisi dari i in ac dan bc (karena ah).
Tidak

15

Kata externkunci mengambil bentuk yang berbeda tergantung pada lingkungan. Jika deklarasi tersedia, externkata kunci menggunakan tautan seperti yang ditentukan sebelumnya di unit terjemahan. Dengan tidak adanya deklarasi semacam itu, externtentukan hubungan eksternal.

static int g();
extern int g(); /* g has internal linkage */

extern int j(); /* j has tentative external linkage */

extern int h();
static int h(); /* error */

Berikut adalah paragraf yang relevan dari draft C99 (n1256):

6.2.2 Keterkaitan pengidentifikasi

[...]

4 Untuk pengidentifikasi yang dideklarasikan dengan extern kelas penypesifikasi penyimpanan dalam ruang lingkup di mana deklarasi sebelumnya dari pengenal itu terlihat, 23) jika deklarasi sebelumnya menentukan hubungan internal atau eksternal, hubungan pengidentifikasi pada deklarasi selanjutnya adalah sama. sebagai tautan yang ditentukan pada deklarasi sebelumnya. Jika tidak ada deklarasi sebelumnya yang terlihat, atau jika deklarasi sebelumnya tidak menetapkan tautan, maka pengidentifikasi memiliki tautan eksternal.

5 Jika deklarasi pengidentifikasi untuk suatu fungsi tidak memiliki specifier kelas penyimpanan, keterkaitannya ditentukan persis seolah-olah dideklarasikan dengan extern specifier kelas penyimpanan. Jika deklarasi pengidentifikasi untuk objek memiliki cakupan file dan tidak ada specifier kelas penyimpanan, hubungannya adalah eksternal.


Apakah ini standar, atau Anda hanya memberi tahu saya perilaku kompiler yang khas? Dalam hal standar, saya akan senang untuk tautan ke standar. Tapi terima kasih
Elazar Leibovich

Ini adalah perilaku standar. Draf C99 tersedia di sini: < open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf >. Standar yang sebenarnya tidak gratis (draft cukup baik untuk sebagian besar keperluan).
dirkgently

1
Saya baru saja mengujinya dalam gcc dan keduanya "extern int h (); static int h () {return 0;}" dan "int h (); static int h () {return 0;}" diterima dengan sama peringatan. Apakah hanya C99 dan bukan ANSI? Bisakah Anda merujuk saya ke bagian yang tepat dalam konsep, karena ini sepertinya tidak berlaku untuk gcc.
Elazar Leibovich

Periksa lagi. Saya mencoba hal yang sama dengan gcc 4.0.1 dan saya mendapatkan kesalahan di mana seharusnya. Coba compiler online atau codepad.org juga datangau jika Anda tidak memiliki akses ke kompiler lain. Baca standar.
dirkgently

2
@ luar biasa, pertanyaan saya sebenarnya adalah apakah ada efek untuk menggunakan exetrn dengan deklarasi fungsi, dan jika tidak ada mengapa mungkin untuk menambahkan extern ke deklarasi fungsi. Dan jawabannya adalah tidak, tidak ada efek, dan pernah ada efek dengan kompiler yang tidak terlalu standar.
Elazar Leibovich

11

Fungsi sebaris memiliki aturan khusus tentang apa externartinya. (Perhatikan bahwa fungsi sebaris adalah ekstensi C99 atau GNU; mereka tidak dalam C. asli

Untuk fungsi non-inline, externtidak diperlukan karena diaktifkan secara default.

Perhatikan bahwa aturan untuk C ++ berbeda. Misalnya, extern "C"diperlukan pada deklarasi C ++ fungsi C yang akan Anda panggil dari C ++, dan ada aturan yang berbeda tentang inline.


Ini adalah satu-satunya jawaban di sini yang keduanya benar dan benar-benar menjawab pertanyaan.
robinjam

4

TKI, eksternal itu berlebihan, dan tidak melakukan apa pun.

Itu sebabnya, 10 tahun kemudian:

Lihat komit ad6dad0 , komit b199d71 , komit 5545442 (29 Apr 2019) oleh Denton Liu ( Denton-L) .
(Digabung oleh Junio ​​C Hamano - gitster- dalam komit 4aeeef3 , 13 Mei 2019)

*.[ch]: hapus externdari deklarasi fungsi menggunakanspatch

Telah ada dorongan untuk menghapus externdari deklarasi fungsi.

Hapus beberapa contoh " extern" untuk deklarasi fungsi yang ditangkap oleh Coccinelle.
Perhatikan bahwa Coccinelle memiliki beberapa kesulitan dengan fungsi pemrosesan dengan __attribute__atau vararg sehingga beberapa externdeklarasi tertinggal untuk ditangani di patch masa depan.

Ini adalah patch Coccinelle yang digunakan:

  @@
    type T;
    identifier f;
    @@
    - extern
    T f(...);

dan dijalankan dengan:

  $ git ls-files \*.{c,h} |
    grep -v ^compat/ |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place

Ini tidak selalu mudah:

Lihat komit 7027f50 (04 Sep 2019) oleh Denton Liu ( Denton-L) .
(Digabung oleh Denton Liu - Denton-L- in commit 7027f50 , 05 Sep 2019)

compat/*.[ch]: hapus externdari deklarasi fungsi menggunakan spatch

Pada 5545442 ( *.[ch]: hapus externdari deklarasi fungsi menggunakan spatch, 2019-04-29, Git v2.22.0-rc0), kami menghapus externs dari deklarasi fungsi menggunakan spatchtetapi kami sengaja mengecualikan file di bawah compat/karena beberapa secara langsung disalin dari upstream dan kami harus menghindari mengaduk mereka sehingga menggabungkan pembaruan di masa mendatang akan lebih mudah.

Di komit terakhir, kami menentukan file yang diambil dari hulu sehingga kami dapat mengecualikannya dan menjalankan spatchsisanya.

Ini adalah patch Coccinelle yang digunakan:

@@
type T;
identifier f;
@@
- extern
  T f(...);

dan dijalankan dengan:

$ git ls-files compat/\*\*.{c,h} |
    xargs spatch --sp-file contrib/coccinelle/noextern.cocci --in-place
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Coccinelle memiliki beberapa masalah dalam menangani __attribute__dan vararg sehingga kami menjalankan yang berikut untuk memastikan bahwa tidak ada perubahan yang tersisa yang tertinggal:

$ git ls-files compat/\*\*.{c,h} |
    xargs sed -i'' -e 's/^\(\s*\)extern \([^(]*([^*]\)/\1\2/'
$ git checkout -- \
    compat/regex/ \
    compat/inet_ntop.c \
    compat/inet_pton.c \
    compat/nedmalloc/ \
    compat/obstack.{c,h} \
    compat/poll/

Perhatikan bahwa dengan Git 2.24 (Q4 2019), semua spurious externdijatuhkan.

Lihat komit 65904b8 (30 Sep 2019) oleh Emily Shaffer ( nasamuffin) .
Dibantu-oleh: Jeff King ( peff) .
Lihat komit 8464f94 (21 Sep 2019) oleh Denton Liu ( Denton-L) .
Dibantu-oleh: Jeff King ( peff) .
(Digabung oleh Junio ​​C Hamano - gitster- dalam komit 59b19bc , 07 Okt 2019)

promisor-remote.h: jatuhkan externdari deklarasi fungsi

Selama pembuatan file ini, setiap kali deklarasi fungsi baru diperkenalkan, itu termasuk a extern.
Namun, mulai dari 5545442 ( *.[ch]: hapusextern dari deklarasi fungsi menggunakan spatch, 2019-04-29, Git v2.22.0-rc0), kami telah secara aktif mencoba untuk mencegah externs digunakan dalam deklarasi fungsi karena mereka tidak perlu.

Hapus ini palsu extern.


3

Kata externkunci memberi tahu kompiler bahwa fungsi atau variabel memiliki hubungan eksternal - dengan kata lain, itu terlihat dari file selain dari yang didefinisikan. Dalam pengertian ini memiliki arti yang berlawanan dengan statickata kunci. Agak aneh untuk diletakkan externpada saat definisi, karena tidak ada file lain yang memiliki visibilitas definisi (atau itu akan menghasilkan banyak definisi). Biasanya Anda meletakkan externdeklarasi di beberapa titik dengan visibilitas eksternal (seperti file header) dan meletakkan definisi di tempat lain.


2

mendeklarasikan fungsi extern berarti bahwa definisinya akan diselesaikan pada saat menghubungkan, bukan pada saat kompilasi.

Tidak seperti fungsi biasa, yang tidak dinyatakan sebagai eksternal, ia dapat didefinisikan di salah satu file sumber (tetapi tidak dalam beberapa file sumber jika tidak, Anda akan mendapatkan kesalahan linker yang mengatakan bahwa Anda telah memberikan banyak definisi fungsi) termasuk yang ada di yang dinyatakan extern. Jadi, dalam hal ini linker menyelesaikan definisi fungsi dalam file yang sama.

Saya tidak berpikir melakukan ini akan sangat berguna namun melakukan eksperimen semacam itu memberikan wawasan yang lebih baik tentang bagaimana kompiler dan penghubung bahasa bekerja.


2
TKI, eksternal itu berlebihan, dan tidak melakukan apa pun. Akan jauh lebih jelas jika Anda mengatakannya demikian.
Elazar Leibovich

@ ElazarLeibovich Saya baru saja menemukan kasus serupa di basis kode kami dan memiliki kesimpulan yang sama. Semua jawaban melalui sini dapat disimpulkan dalam satu liner Anda. Ini tidak memiliki efek praktis tetapi mungkin bagus untuk dibaca. Senang melihat Anda online dan tidak hanya di pertemuan :)
Aviv

1

Alasan tidak ada efeknya adalah karena pada saat tautan linker mencoba menyelesaikan definisi eksternal (dalam kasus Anda extern int f()). Tidak masalah jika ditemukan di file yang sama atau file yang berbeda, asalkan ditemukan.

Semoga ini menjawab pertanyaan Anda.


1
Lalu mengapa mengizinkan untuk menambahkan externfungsi apa saja?
Elazar Leibovich

2
Harap jangan menempatkan spam yang tidak terkait dalam posting Anda. Terima kasih!
Mac
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.