Parametresiz bir işlev (gerçek işlev tanımına kıyasla) neden derleniyor?


388

Ben sadece neden derleme konusunda kafam karıştı birinin C kodu rastlamak. Anlamadığım iki nokta var.

İlk olarak, işlev prototipinin gerçek işlev tanımına kıyasla hiçbir parametresi yoktur. İkinci olarak, işlev tanımındaki parametrenin bir türü yoktur.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

Bu neden işe yarıyor? Birkaç derleyicide test ettim ve iyi çalışıyor.


77
Bu K&R C. 1980'lerde tam işlev prototipleri olmadan böyle bir kod yazdık.
hughdbrown

6
gcc ile uyarmak yok -Wstrict-prototypeshem int func()ve int main()xc: 3: Uyarı: işlev bildirimi bir prototip değil. Sen ilan etmeli main()yanı main(void)sıra.
Jens

3
@Jens Soruyu neden düzenlediniz? Bu noktayı kaçırmışsınız gibi görünüyor ...
dlras2

1
Ben sadece örtük int'i açık hale getirdim. Bu noktayı nasıl özlüyor? Mesele şu ki neden int func();uyumlu int func(arglist) { ... }?
Jens

3
@MatsPetersson Bu yanlış. C99 5.1.2.2.1 iddianızla açıkça çelişir ve argüman versiyonunun olmadığını belirtir int main(void).
Jens

Yanıtlar:


271

Diğer tüm cevaplar doğrudur, ancak sadece tamamlanması için

Bir işlev aşağıdaki şekilde bildirilir:

  return-type function-name(parameter-list,...) { body... }

return-type , işlevin döndürdüğü değişken türüdür. Bu bir dizi türü veya bir işlev türü olamaz. Verilmezse int kabul edilir. .

fonksiyon adı , .

parametre listesi , işlevin virgülle ayırdığı parametrelerin listesidir. Herhangi bir parametre verilmezse, işlev herhangi bir şey almaz ve boş bir parantez kümesiyle veya void anahtar sözcüğüyle tanımlanmalıdır. Parametre listesindeki bir değişkenin önünde hiçbir değişken türü yoksa, int kabul edilir . Diziler ve işlevler işlevlere aktarılmaz, ancak otomatik olarak işaretleyicilere dönüştürülür. Liste üç nokta (, ...) ile sonlandırılırsa, ayarlanmış sayıda parametre yoktur. Not: stdarg.h başlığı, üç nokta kullanılırken bağımsız değişkenlere erişmek için kullanılabilir.

Ve yine bütünlük uğruna. C11 teknik özellik 6: 11: 6'dan (sayfa: 179)

Boş parantez fonksiyonlu declarators kullanımı (değil prototip format parametresinin türü declarators) bir eskimiş bir özelliktir .


2
"Parametre listesindeki bir değişkenin önünde değişken türü yoksa, int kabul edilir." Bunu sizin sağlanan bağlantıda görüyorum, ancak herhangi bir standart c89, c99'da bulamıyorum ... Başka bir kaynak sağlayabilir misiniz?
godaygo

"Hiçbir parametre verilmezse, işlev herhangi bir şey almaz ve boş bir parantez kümesiyle tanımlanmalıdır" Tony The Lion'un cevabına aykırı geliyor ama Tony The Lion'un cevabının zor olduğunu doğru öğrendim.
jakun

160

C'de func()istediğiniz sayıda argüman iletebileceğiniz anlamına gelir. Eğer herhangi bir argüman istemiyorsanız o zaman ilan etmelisiniz func(void). Belirtilmezse, işlevinize ilettiğiniz tür varsayılan olarak kullanılır int.


3
Aslında int için varsayılan tek bir türü yoktur. Aslında, tüm argümanlar varsayılan olarak int (hatta dönüş türü). func(42,0x42);( Tüm çağrıların iki argüman formunu kullanması gereken) aramak mükemmel .
Jens

1
C'de belirtilmemiş türlerin varsayılan olarak int gibi varsayılan olması , örneğin bir değişken bildirdiğinizde oldukça yaygındır : unsigned x; X değişkeni ne tür? İmzasız int
bitek

4
Daha doğrusu örtülü int söyleyebilirim oldu Eski günlerde yaygın. Kesinlikle artık değil ve C99'da C'den kaldırıldı
Jens

2
Bu cevap boş parametre listeleri ile fonksiyon prototipleri için geçerli olduğunu belirtmek gerekir olabilir, ama değil boş parametre listeleri ile tanımlarını çalışır.
otistik

@mnemonicflow "int için varsayılan olarak belirtilmemiş bir tür" değildir; unsignedile aynı türden başka bir addır unsigned int.
Ocaklar

58

int func();C standardının olmadığı günlerden yani K&R C günlerinden (1989'dan önce ilk "ANSI C" standardının yayınlandığı yıl) eskimiş bir işlev beyanıdır .

Olduğunu unutmayın K & R C hiçbir prototipler ve anahtar kelime voidhenüz icat edilmedi. Tüm yapabileceğiniz derleyiciye bir işlevin dönüş türü hakkında bilgi vermekti . K&R C'deki boş parametre listesi, "belirtilmemiş ancak sabit" sayıda argüman anlamına gelir. Sabit, fonksiyonu her seferinde aynı sayıda argümanla çağırmanız gerektiği anlamına gelir ( numara ve türün her çağrı için değişebileceği gibi değişken bir fonksiyonun aksine printf).

Birçok derleyici bu yapıyı teşhis edecektir; özellikle gcc -Wstrict-prototypessize "fonksiyon bildirimi bir prototip değildir" diyecektir. bir prototipe (özellikle C ++ ile zehirliyseniz!), ama değil. Bu eski bir stil K&R C dönüş tipi bildirimi.

Temel kural: Asla boş bir parametre listesi bildirimini boş bırakmayın, int func(void)özel olarak kullanın . Bu, K&R dönüş türü bildirimini uygun bir C89 prototipine dönüştürür. Derleyiciler mutlu, geliştiriciler mutlu, statik dama mutlu. C ++ Wfond tarafından yanlış yönlendirilenler, yabancı dil becerilerini kullanmaya çalıştıklarında ekstra karakterler yazmaları gerektiğinden, yanabilirler :-)


1
Lütfen bunu C ++ ile ilişkilendirmeyin. Öyle sağduyu boş argüman listesi anlamına geldiğini hiçbir parametre ve dünyadaki insanlar yaklaşık 40 yıl önce K & F adamlar tarafından yapılan hatalar başa ilgilenmiyor. Ancak komite uzmanları, kontrüral seçimleri C99, C11'e sürüklemeye devam ediyor.
pfalcon

1
@pfalcon Sağduyu oldukça özneldir. Bir insan için sağduyu, başkalarına açık deliliktir. Profesyonel C programcıları boş parametre listelerinin belirtilmemiş ancak sabit sayıda argümanı gösterdiğini bilir. Bunu değiştirmek muhtemelen birçok uygulamayı kırabilir. Sessiz değişikliklerden kaçınmak için komiteleri suçlamak yanlış ağaç IMHO'yu havlıyor.
Jens

53
  • Boş parametre listesi "herhangi bir argüman" anlamına gelir, dolayısıyla tanım yanlış değildir.
  • Eksik tipin olduğu varsayılır int.

Ben bu yapılandırılmış uyarı / hata düzeyinde olsa eksik geçen herhangi bir yapı düşünürdüm, bu gerçek kod için izin olmak bir anlamı yoktur.


30

Bu var K & R tarzı işlev bildirimi ve tanım. C99 Standardından (ISO / IEC 9899: TC3)

Bölüm 6.7.5.3 Fonksiyon Bildiricileri (prototipler dahil)

Tanımlayıcı listesi, yalnızca işlevin parametrelerinin tanımlayıcılarını bildirir. Bir işlev bildirimindeki, o işlevin tanımının bir parçası olan boş bir liste, işlevin parametresinin olmadığını belirtir. Bir işlev bildirimindeki, o işlevin tanımının bir parçası olmayan boş liste, parametrelerin sayısı veya türleri hakkında hiçbir bilgi sağlanmadığını belirtir. (Her iki işlev türü de "eski stil" ise, parametre türleri karşılaştırılmaz.)

Bölüm 6.11.6 Fonksiyon bildiricileri

Boş parantezli işlev bildiricilerin kullanımı (prototip biçimli parametre türü bildiriciler değil) eskimiş bir özelliktir.

Bölüm 6.11.7 İşlev tanımları

Fonksiyon tanımlarının ayrı parametre tanımlayıcıları ve bildirim listeleriyle (prototip formatı parametre tipi ve tanımlayıcıları değil) kullanılması eskimiş bir özelliktir.

Eski tarz K&R demek tarzı

Misal:

Beyan: int old_style();

Tanım:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}

16

C int, işlev dönüş türü ve parametre listesinde herhangi bir tür belirtilmediğini varsayar . Sadece bu kural için garip şeyler takip etmek mümkündür.

İşlev tanımı şuna benzer.

int func(int param) { /* body */}

Eğer bir prototip yazıyorsanız

int func(int param);

Prototipte sadece parametre türünü belirleyebilirsiniz. Parametrelerin adı zorunlu değildir. Yani

int func(int);

Ayrıca parametre türünü belirtmezseniz ancak ad tür intolarak kabul edilirse .

int func(param);

Daha ileri giderseniz, takip etmek de işe yarar.

func();

Derleyici int func()yazdığınızı varsayar func(). Ama func()bir işlev gövdesi içine koymayın . Bu bir işlev çağrısı olacak


3
Boş bir parametre listesi, örtük int türüyle ilişkili değildir. int func()örtük bir biçimi değildir int func(int).
John Bartholomew

11

@Krishnabhadra'nın belirttiği gibi, diğer kullanıcılardan önceki tüm yanıtların doğru bir yorumu var ve sadece bazı noktaların daha ayrıntılı bir analizini yapmak istiyorum.

ANSI-C'de olduğu gibi Old-C'de " türlenmemiş resmi parametre ", 8bit MPU'da çalışma kaydınızın veya talimat derinliği kapasitesinin (gölge kayıtları veya talimat birikimli döngüsü) boyutlarını alın 16bit'te bir int16 olacaktır MPU ve benzeri bir int16 vb. Olacaksa, 64bit mimariler aşağıdaki gibi seçenekleri derlemeyi seçebilir: -m32.

Yüksek düzeyde daha basit bir uygulama gibi görünse de, birden fazla parametreyi geçmek için, kontrol dimencion veri tipi adımında programcının çalışması daha zorlu hale gelir.

Diğer durumlarda, bazı mikroişlemci mimarileri için özelleştirilmiş ANSI derleyicileri, kodun kullanımını optimize etmek için bu eski özelliklerden bazılarını kullandı ve bu "türlenmemiş resmi parametrelerin" konumunu çalışma kaydının içinde veya dışında çalışmaya zorladı, bugün neredeyse "uçucu" ve "kayıt" kullanımı ile aynıdır.

Ancak en modern derleyicilerin, iki tür parametre bildirimi arasında herhangi bir ayrım yapmadığı unutulmamalıdır.

Linux altında gcc ile derleme örnekleri:

main.c

main2.c

main3.c  
Her durumda, prototipin yerel olarak ifadesinin bir faydası yoktur, çünkü bu prototipe referans veren parametreler olmadan çağrı yapılmayacaktır. Sistemi "türlenmemiş resmi parametre" ile kullanıyorsanız, harici bir çağrı için, bildirici bir prototip veri türü oluşturmaya devam edin.

Bunun gibi:

int myfunc(int param);

5
Görüntülerin bayt cinsinden boyutunun mümkün olan en düşük düzeyde olması için görüntüleri optimize etme sorununu çektim. Ben resimler oluşturulan kod farkı eksikliği hızlı bir görünüm alabilirsiniz düşünüyorum. Kodu test ediyorum, sadece sorun hakkında teorik değil, iddia ettiği şeyin gerçek belgelerini üretiyorum. ama hepsi dünyayı aynı şekilde görmez. Çok rahatsız ettiğim için topluluğa daha fazla bilgi sağlama girişimim çok üzücü.
RTOSkit

1
intHangisinin daha küçükse, genellikle çalışma kaydı boyutu veya 16 bit olan belirtilmemiş bir dönüş türünün her zaman olduğundan eminim .
supercat

@supercat +1 Mükemmel kesinti, haklısın! Bu tam olarak yukarıda tartıştığım şeydir, varsayılan olarak belirli bir mimari için tasarlanmış bir derleyici, her zaman söz konusu CPU / MPU'nun çalışma kaydının boyutunu yansıtır, Bu nedenle gömülü bir ortamda, çeşitli yeniden yazma stratejileri vardır. gerçek zamanlı işletim sistemi, bir derleyici katmanına (stdint.h) veya taşınabilirlik katmanına taşınabilir, bu da taşınabilirliği diğer birçok arşitte yapar ve CPU / MPU'ya özgü türlerin (int, uzun, uzun uzun, vb.) u8, u16, u32 genel sistem tipleriyle.
RTOSkit

@supercat Bir işletim sistemi veya belirli bir derleyici tarafından sunulan kontrol türleri olmadan, geliştirme süresinde biraz ilgilenmeniz gerekir ve bir "bulma" ödevlerinin " 16bit int "ve" 32bit int "değil.
RTOSkit

@RTOSkit: Yanıtınız, 8 bit işlemcideki varsayılan türün bir Int8 olacağını gösterir. PICmicro marka mimarisi için durumun olduğu birkaç C-ish derleyicisini hatırlıyorum, ancak bir C standardına uzaktan benzeyen bir şeyin, her intikisi de aralıktaki tüm değerleri tutamayan bir türe izin verdiğini düşünmüyorum - 32767 ila +32767 (not -32768 gerekli değildir) ve ayrıca tüm chardeğerleri tutabilir ( char16 bit ise, imzalanması veya intdaha büyük olması gerekir).
supercat

5

Parametre türüyle ilgili olarak, burada zaten doğru cevaplar var, ancak derleyiciden duymak istiyorsanız bazı bayraklar eklemeyi deneyebilirsiniz (bayraklar her zaman zaten her zaman iyi bir fikirdir).

kullanarak program derleme gcc foo.c -Wextraolsun:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

garip bir şekilde -Wextrabunu yakalamıyor clang(bir -Wmissing-parameter-typenedenden dolayı, belki de yukarıda bahsedilen tarihi olanlar için tanımıyor ) ama -pedantic:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

Ve yine yukarıda belirtildiği gibi prototip sorunu için, int func()bunu açıkça beklediğiniz gibi tanımlayamazsanız, rastgele parametreleri ifade eder int func(void):

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

veya clangşu şekilde:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.

3

İşlev bildiriminde herhangi bir parametre yoksa boş ise, belirtilmemiş sayıda argüman alıyor demektir. Herhangi bir argüman almamasını istiyorsanız, bunu şu şekilde değiştirin:

int func(void);

1
"İşlev prototipinde parametre yoksa" Bu bir işlev prototipi değil, yalnızca bir işlev bildirimi.
effeffe

@effeffe bunu düzeltti. Bu sorunun çok dikkat edeceğini fark etmedim;)
PP

3
"İşlev prototipi" yalnızca "işlev bildirimi" için alternatif (muhtemelen eski moda) bir ifade değil midir?
Giorgio

@Giorgio IMO, ikisi de doğru. "işlev prototipi", "işlev tanımı" ile eşleşmelidir. Muhtemelen effeffe'nin sorudaki beyan anlamına geldiğini ve cevabımda ne demek istediğimi düşünüyorum.
PP

@Giorgio no, işlev bildirimi dönüş türü değeri, tanımlayıcı ve isteğe bağlı bir parametre listesi ile yapılır; işlev prototipleri, bir parametre listesiyle birlikte işlev bildirimidir.
effeffe

0

Bu yüzden insanlar genellikle kodlarını derlemek için tavsiye:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

Bu bayraklar birkaç şeyi zorlar:

  • -Wmissing-değişken-bildirimleri: Önce bir prototip almadan statik olmayan bir işlevi bildirmek imkansızdır. Bu, bir başlık dosyasındaki bir prototipin gerçek tanımla eşleşmesi olasılığını artırır. Alternatif olarak, genel olarak görünür olması gerekmeyen işlevlere statik anahtar sözcüğü eklemenizi zorunlu kılar.
  • -Wstrict-değişken-bildirimleri: Prototip argümanları düzgün bir şekilde listelemelidir.
  • -Wold-style-definition: İşlev tanımının kendisi de bağımsız değişkenleri düzgün bir şekilde listelemelidir.

Bu bayraklar ayrıca birçok Açık Kaynak projesinde varsayılan olarak kullanılır. Örneğin, FreeBSD, Makefile'nizde WARNS = 6 ile oluştururken bu bayrakları etkinleştirmiştir.

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.