Extern anahtar sözcüğünün C işlevleri üzerindeki etkileri


172

C de, externfonksiyon bildiriminden önce kullanılan anahtar kelimenin hiçbir etkisini fark etmedim . İlk başta, extern int f();tek bir dosyada tanımlarken dosyayı dosyanın kapsamı dışında uygulamaya zorladığını düşündüm . Ancak ben her ikisi de öğrendim:

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

ve

extern int f() {return 0;}

gcc uyarıları olmadan, sadece iyi derleyin. Kullandım gcc -Wall -ansi; //yorumları bile kabul etmezdi .

extern İşlev tanımlarından önce kullanmanın herhangi bir etkisi var mı? Veya yalnızca işlevler için yan etkisi olmayan isteğe bağlı bir anahtar kelime.

İkinci durumda, standart tasarımcıların dilbilgisini gereksiz anahtar kelimelerle neden atmayı seçtiklerini anlamıyorum.

DÜZENLEME: Ben kullanım için olduğunu biliyorum, netleştirmek için externdeğişkenlerde, ama sadece yaklaşık soruyorum externiçinde fonksiyonlar .


Bazı çılgın şablonlar için bunu kullanmaya çalışırken yaptığım bazı araştırmalara göre, extern çoğu derleyici tarafından tasarlandığı şekilde desteklenmiyor ve bu yüzden gerçekten hiçbir şey yapmıyor.
Ed James

4
Her zaman gereksiz değil, cevabımı gör. Genel bir üstbilgide istemediğiniz modüller arasında bir şey paylaşmanız gerektiğinde çok kullanışlıdır. Bununla birlikte, bir kamu başlığındaki (modern derleyicilerle) her bir işlevi `` dışarı çıkarmak '', kendi başlarına anlayabilecekleri için çok az ya da hiç faydası yoktur.
Tim Post

@Ed .. eğer uçucu int foo foo.c'de globalse ve bar.c buna ihtiyaç duyuyorsa, bar.c bunu extern olarak ilan etmelidir. Avantajları var. Bunun ötesinde, genel bir üstbilgide gösterilmesini istemediğiniz bazı işlevleri paylaşmanız gerekebilir.
Tim Post


2
@ Eğer varsa, diğer soru bunun bir kopyasıdır. 2009 vs 2012
Elazar Leibovich

Yanıtlar:


138

Foo.c ve bar.c olmak üzere iki dosyamız var.

İşte 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;
}

Şimdi burada bar.c

#include <stdio.h>

extern volatile unsigned int stop_now;

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

Gördüğünüz gibi, foo.c ve bar.c arasında paylaşılan bir başlığımız yok, ancak bar.c bağlandığında foo.c'de bildirilen bir şeye ihtiyaç duyuyor ve foo.c bağlandığında bar.c'den bir işleve ihtiyaç duyuyor.

'Extern' kullanarak derleyiciye, takip eden her şeyin bağlantı anında (statik olmayan) bulunacağını söylüyorsunuz; daha sonra karşılaşacağınız için geçerli geçişte hiçbir şey ayırmayın. Fonksiyonlar ve değişkenler bu konuda eşit olarak ele alınır.

Modüller arasında bir miktar global paylaşmanız gerekiyorsa ve bunu bir başlığa koymak / başlatmak istemiyorsanız çok kullanışlıdır.

Teknik olarak, bir kütüphane genel başlığındaki her fonksiyon 'extern'tir, ancak bunları böyle etiketlemek derleyiciye bağlı olarak çok az veya hiç faydası yoktur. Çoğu derleyici bunu kendi başına çözebilir. Gördüğünüz gibi, bu işlevler aslında başka bir yerde tanımlanmıştır.

Yukarıdaki örnekte, main () merhaba dünyasını yalnızca bir kez basar, ancak bar_function () girmeye devam eder. Ayrıca, bar_function () işlevinin bu örnekte geri dönmeyeceğini unutmayın (bu sadece basit bir örnek olduğu için). Yeterince pratik görünmüyorsa, bir sinyale hizmet verildiğinde (dolayısıyla geçici) stop_now değerinin değiştirildiğini hayal edin.

Externs, sinyal işleyicileri, üstbilgi veya yapıya koymak istemediğiniz bir muteks, vb. bunu nesnenin tanımlandığı modülde saklayacağız. Bununla birlikte, kamu işlevlerini prototiplendirirken modern derleyicilerle belirtmenin çok az anlamı vardır.

Umarım yardımcı olur :)


56
Kodunuz bar_function'dan önce extern olmadan derlenecektir.
Elazar Leibovich

2
@Tim: O zaman birlikte çalıştığım kodla çalışmanın şüpheli imtiyazına sahip değilsin. Olabilir. Bazen üstbilgide statik işlev tanımı da bulunur. Çirkin ve gereksiz% 99.99 (Bir sipariş veya iki veya büyüklükle kapalı olabilirim, ne sıklıkta gerekli olduğunu abartmış olabilirim). Genellikle, bir başlığın yalnızca diğer kaynak dosyaları bilgileri kullanacağı zaman bir başlığa ihtiyaç duyulduğunu yanlış anladığında ortaya çıkar; üstbilgi (ab) bir kaynak dosya için bildirim bilgilerini saklamak için kullanılır ve başka hiçbir dosyanın bu dosyayı içermesi beklenmez. Bazen, daha çarpık nedenlerle ortaya çıkar.
Jonathan Leffler

2
@Jonathan Leffler - Gerçekten de şüpheli! Daha önce bazı kabataslak kodları miras aldım, ama dürüstçe söyleyebilirim ki, birisinin bir başlığa statik bir bildirim koyduğunu hiç görmedim. Oldukça eğlenceli ve ilginç bir işiniz var gibi görünüyor :)
Tim Post

1
'Üstbilgide olmayan işlev prototipi'nin dezavantajı, işlev tanımı bar.cve içindeki bildirim arasındaki tutarlılığın otomatik olarak bağımsız denetimini almamanızdır foo.c. İşlev bildirilirse foo.h ve her iki dosya da içeriyorsa foo.h, başlık iki kaynak dosya arasında tutarlılığı zorlar. Onsuz tanımı ise bar_functionde bar.cdeğişikliklere ama deklarasyon foo.cdeğiştirilmez, işler çalışma anında ters gidebilir; derleyici sorunu tespit edemez. Bir başlık düzgün bir şekilde kullanıldığında, derleyici sorunu bulur.
Jonathan Leffler

1
işlev bildirimleri üzerindeki extern, 'unsigned int' içindeki 'int' gibi gereksizdir. Prototip ileri bir beyan DEĞİLDİR, 'extern' kullanmak iyi bir uygulamadır ... Fakat görev bir son durum olmadığı sürece gerçekten 'extern' olmadan bir başlıkta yaşamalıdır. stackoverflow.com/questions/10137037/…

82

Standardı hatırladığım kadarıyla, tüm işlev bildirimleri varsayılan olarak "extern" olarak kabul edilir, bu yüzden açıkça belirtmeye gerek yoktur.

Bu, bu anahtar kelimeyi yararsız yapmaz çünkü değişkenlerle de kullanılabilir (ve bu durumda - bağlantı sorunlarını çözmek için tek çözümdür). Ancak işlevlerle - evet, isteğe bağlıdır.


21
Sonra standart tasarımcı olarak, sadece dilbilgisine gürültü eklediğinden, işlevlerle birlikte extern kullanmaya izin vermeyeceğim .
Elazar Leibovich

3
Geriye dönük uyumluluk bir acı olabilir.
MathuSum Mut

1
@ElazarLeibovich Aslında, bu özel durumda, izin verilmemesi dilbilgisine gürültü katacaktır.
Orbit'te Hafiflik Yarışları

1
Bir anahtar kelimeyi nasıl sınırlandırdığımda gürültü eklenir , ancak sanırım bu bir zevk meselesi.
Elazar Leibovich

Diğer programcılara işlevin geçerli dosyada değil, başka bir dosyada tanımlandığını ve içerilen başlıklardan birinde bildirilmediğini belirttiği için işlevler için "extern" kullanımına izin vermek yararlıdır.
DimP

23

İki ayrı kavramı birbirinden ayırmanız gerekir: işlev tanımı ve sembol bildirimi. "extern" bir bağlantı modifiye edicisidir, derleyiciye daha sonra değinilen sembolün nerede tanımlandığı hakkında bir ipucudur (ipucu "burada değil").

Yazarsam

extern int i;

bir C dosyasındaki dosya kapsamında (bir fonksiyon bloğunun dışında) "değişken başka bir yerde tanımlanabilir" diyorsunuz.

extern int f() {return 0;}

hem f işlevinin bir açıklaması hem de f işlevinin bir tanımıdır. Bu durumda tanım extern'i geçersiz kılar.

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

önce bir deklarasyon, ardından tanım.

externDosya kapsamı değişkenini bildirmek ve aynı anda tanımlamak istiyorsanız , kullanımı yanlıştır. Örneğin,

extern int i = 4;

derleyiciye bağlı olarak bir hata veya uyarı verecektir.

externBir değişkenin tanımından açıkça kaçınmak istiyorsanız , kullanımı yararlıdır.

Açıklamama izin ver:

Diyelim ki ac dosyası şunları içeriyor:

#include "a.h"

int i = 2;

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

Ah dosyası şunları içerir:

extern int i;
int f(void);

ve bc dosyası şunları içerir:

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

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

Üstbilgideki extern kullanışlıdır, çünkü derleyiciye bağlantı aşamasında "bu bir tanımdır, bir tanım değildir" der. İ'yi tanımlayan, bunun için yer ayıran ve ona bir değer atayan ac satırını kaldırırsam, program tanımsız bir başvuru ile derlenemez. Bu, geliştiriciye bir değişkenden bahsettiğini ancak henüz tanımlanmadığını bildirir. Öte yandan, "extern" anahtar kelimesini atlar ve int i = 2satır kaldırırsanız , program hala derler - i 0 varsayılan değeri ile tanımlanacaktır.

Dosya kapsamı değişkenleri, bir işlevin üstünde bildirdiğiniz blok kapsamı değişkenlerinin aksine, onlara açıkça bir değer atamazsanız, varsayılan 0 veya NULL değeriyle örtülü olarak tanımlanır. Extern anahtar sözcüğü bu örtülü tanımdan kaçınır ve böylece hataların önlenmesine yardımcı olur.

İşlevler için, işlev bildirimlerinde, anahtar sözcük gerçekten gereksizdir. İşlev bildirimlerinin üstü kapalı bir tanımı yoktur.


int i = 2-3'üncü paragraftaki satırı kaldırmak mı istediniz ? Ve int i;derleyicinin bu değişken için bellek ayıracağını, ancak extern int i;derleyicinin bellek ayırmayacağını ancak değişkeni başka bir yerde arayacağını belirtmek doğru mu?
Donmuş Alev

Aslında "extern" anahtar sözcüğünü atlarsanız, program i ve bc'de i'nin (ah nedeniyle) yeniden tanımlanması nedeniyle derlenmez.
Nixt

15

externAnahtar kelime ortamına bağlı olarak farklı biçimler alır. Bir bildirim varsa, externanahtar kelime bağlantıyı daha önce çeviri biriminde belirtildiği şekilde alır. Bu tür bir bildirimin yokluğunda, externdış bağlantıyı belirtir.

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 */

İşte C99 taslağından (n1256) ilgili paragraflar:

6.2.2 Tanımlayıcıların bağlantıları

[...]

4 Bu tanımlayıcının önceki bir bildiriminin görülebildiği bir kapsamda depolama sınıfı belirleyici extern ile bildirilen bir tanımlayıcı için, 23) önceki bildirim iç veya dış bağlantıyı belirtiyorsa, tanımlayıcının sonraki bildirimdeki bağlantısı aynıdır önceki beyanda belirtilen bağlantı olarak. Önceden herhangi bir bildirim görünmüyorsa veya önceki bildirimde bağlantı belirtilmemişse, tanımlayıcının harici bağlantısı vardır.

5 Bir işlev için tanımlayıcının bildiriminde depolama sınıfı belirteci yoksa, bağlantısı tam olarak depolama sınıfı belirtici harici ile bildirilmiş gibi belirlenir. Bir nesne için tanımlayıcının bildiriminde dosya kapsamı varsa ve depolama sınıfı belirticisi yoksa, bağlantısı harici olur.


Standart mı yoksa bana tipik bir derleyicinin davranışını mı söylüyorsunuz? Standart olması durumunda, standardın bağlantısı için memnuniyet duyarım. Ama teşekkürler!
Elazar Leibovich

Bu standart davranıştır. C99 taslağını burada bulabilirsiniz: < open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf >. Gerçek standart olsa özgür değildir (taslak çoğu amaç için yeterince iyidir).
dirkgently

1
Ben sadece gcc test ve hem "extern int h (); statik int h () {dönüş 0;}" ve "int h (); statik int h () {dönüş 0;}" aynı ile kabul edilir uyarı. ANSI değil, sadece C99 mu? Beni taslaktaki kesin bölüme yönlendirebilir misiniz, çünkü bu gcc için doğru görünmüyor.
Elazar Leibovich

Tekrar kontrol et. Aynı şeyi gcc 4.0.1 ile de denedim ve olması gereken yerde bir hata alıyorum. Başka derleyicilere erişiminiz yoksa comeau'nun çevrimiçi derleyicisini veya codepad.org'u da deneyin. Standardı okuyun.
dirkgently

2
@dirkgently, Asıl sorum işlev bildirimi ile exetrn kullanmak için herhangi bir etkisi vardır ve hiçbiri yoksa bir işlev bildirimine extern eklemek mümkün değildir. Ve cevap hayır, hiçbir etkisi yok ve bir zamanlar standart olmayan derleyicilerle bir etkisi vardı.
Elazar Leibovich

11

Satır içi işlevlerin ne anlama geldiğiyle ilgili özel kuralları vardırextern . (Satır içi işlevlerin bir C99 veya GNU uzantısı olduğunu, orijinal C'de olmadığını unutmayın.

Satır içi olmayan işlevler için externvarsayılan olarak açık olduğundan gerekli değildir.

C ++ kurallarının farklı olduğunu unutmayın. Örneğin extern "C", C ++ 'dan çağıracağınız C fonksiyonlarının C ++ beyanında gereklidir ve hakkında farklı kurallar vardır inline.


Burada her ikisi de doğru ve aslında soruyu cevaplayan tek cevap budur.
robinjam

4

IOW, extern gereksizdir ve hiçbir şey yapmaz.

Bu yüzden 10 yıl sonra:

Bakınız Den ad6dad0 , taahhüt b199d71 , taahhüt 5545442 (29 Nis 2019) Denton Liu ( Denton-L) .
(Göre Birleştirilmiş - Junio Cı Hamano gitster- içinde 4aeeef3 tamamlama 2019 13 Mayıs)

*.[ch]: externkullanarak işlev bildirimlerinden kaldırspatch

externİşlev bildirimlerinden kaldırılması için bir push yapıldı .

externCoccinelle tarafından yakalanan işlev bildirimleri için bazı " " örneklerini kaldırın .
Coccinelle'in __attribute__veya varargs işlevlerini işlemede bazı zorluklarla karşılaştığından, bazı externbildirimlerin gelecekteki bir yamada ele alınması için geride bırakıldığını unutmayın.

Kullanılan Coccinelle yaması:

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

ve ile çalıştırıldı:

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

Bu her zaman kolay değildir:

Bakınız Denton Liu ( ) tarafından taahhüt edilen 7027f50 (04 Eyl 2019 ) . (Tarafından Birleştirilmiş - Denton Liu - içinde 7027f50 taahhüt 2019, 05 Eyl)Denton-L
Denton-L

compat/*.[ch]: Kaldırmak extern spatch kullanarak işlev bildirimlerinden kaldır

5545442'de ( *.[ch]: spatch externkullanarak işlev bildirimlerinden kaldır, 2019-04-29, Git v2.22.0-rc0),spatch ancak compat/bazıları doğrudan yukarı kopyalandığından kasıtlı olarak dosyaları hariç ve bundan kaçınmalıyız gelecekteki güncellemeleri manuel olarak birleştirmek daha kolay olacak şekilde onları çalkalayın.

Son işlemde, bir akış yukarıdan alınan dosyaları belirledik, böylece onları hariç tutabilir ve çalıştırabiliriz spatch geri kalanı üzerinde için .

Kullanılan Coccinelle yaması:

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

ve ile çalıştırıldı:

$ 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 ile ilgili bazı sorunlar __attribute__ve varargs var, bu yüzden geride hiçbir değişiklik kalmamasını sağlamak için aşağıdakileri yaptık:

$ 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/

Git 2.24 (Q4 2019) ile herhangi bir sahte extern düştüğünü unutmayın.

Bkz. Taahhüt: 65904b8 (30 Eyl 2019) - Emily Shaffer ( nasamuffin) .
Yardım eden: Jeff King ( peff) .
Bakınız Denton Liu ( ) tarafından 8464f94 (21 Eyl 2019) taahhüdü . Yardım eden: Jeff King ( ) . (Tarafından Birleştirilmiş - Junio C Hamano - içinde 59b19bc taahhüt 2019 7 Ekim,)Denton-L
peff
gitster

promisor-remote.h: düşürmek extern işlev bildiriminden

Bu dosyanın oluşturulması sırasında, yeni bir işlev bildirimi her eklendiğinde bir extern.
Ancak, 5545442'den başlayarak ( *.[ch]: kaldırexternspatch 2019-04-29, Git v2.22.0-rc0 kullanarak işlev bildirimlerinden gereksiz oldukları için işlev bildirimlerinde extern'lerin kullanılmasını engellemeye çalışıyoruz.

Bu sahte öğeleri kaldırın extern.


3

externAnahtar kelime bilgilendirir işlev veya değişken dış bağlantı vardır derleyici - o tanımlandığı dışındaki dosyalarından görünür olduğunu, diğer bir deyişle. Bu anlamda staticanahtar kelimenin tam tersi bir anlamı vardır . externTanımlama sırasında koymak biraz gariptir , çünkü başka hiçbir dosya tanımın görünürlüğüne sahip olmayacaktır (veya birden fazla tanımla sonuçlanacaktır). Normalde externharici görünürlük (başlık dosyası gibi) olan bir noktaya bir bildirim koyar ve tanımı başka bir yere koyarsınız.


2

bir işlev extern bildirmek, tanımının derleme sırasında değil bağlantı sırasında çözüleceği anlamına gelir.

Harici olarak bildirilmeyen normal işlevlerden farklı olarak, kaynak dosyalardan herhangi birinde tanımlanabilir (ancak birden fazla kaynak dosyada değil, aksi takdirde işlev dahil olmak üzere işlevin birden fazla tanımını verdiğinizi söyleyen linker hatası alırsınız) Yani, ur durumda linker aynı dosyadaki fonksiyon tanımını çözer.

Bunu yapmanın çok yararlı olacağını düşünmüyorum, ancak bu tür deneyler yapmak dilin derleyicisinin ve bağlayıcısının nasıl çalıştığı hakkında daha iyi bir fikir veriyor.


2
IOW, extern gereksizdir ve hiçbir şey yapmaz. Bu şekilde koyarsanız çok daha açık olur.
Elazar Leibovich

@ElazarLeibovich Kod tabanımızda benzer bir durumla karşılaştım ve aynı sonuca vardım. Buradaki yanıtlar aracılığıyla olanlar, bir astarınızda özetlenebilir. Pratik bir etkisi yoktur, ancak okunabilirlik açısından iyi olabilir. Sizi çevrimiçi olarak görmek güzel ve sadece buluşmalarda değil :)
Aviv

1

Etkisinin olmamasının nedeni, bağlantı zamanında bağlayıcının extern tanımını (sizin durumunuzda extern int f()) çözmeye çalışmasıdır . Bulunduğu sürece aynı dosyada mı yoksa farklı bir dosyada mı bulduğu önemli değil.

Umarım bu soruya cevap verir.


1
O zaman neden externherhangi bir fonksiyona eklemeye izin veriyorsunuz ?
Elazar Leibovich

2
Lütfen yayınlarınıza ilgisiz spam yerleştirmekten kaçının. Teşekkürler!
Mac
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.