Dlaczego kompiluje się funkcję bez parametrów (w porównaniu z rzeczywistą definicją funkcji)?

388

Właśnie natknąłem się na czyjś kod C, którego nie rozumiem, dlaczego się kompiluje. Są dwa punkty, których nie rozumiem.

Po pierwsze, prototyp funkcji nie ma parametrów w porównaniu z rzeczywistą definicją funkcji. Po drugie, parametr w definicji funkcji nie ma typu.

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

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

Dlaczego to działa? Przetestowałem to w kilku kompilatorach i działa dobrze.

AdmiralJonB
źródło
77
To K&R C. Napisaliśmy taki kod w latach 80., zanim pojawiły się prototypy pełnych funkcji.
hughdbrown,
6
gcc ostrzega -Wstrict-prototypesza pomocą zarówno :, jak int func()i int main(): xc: 3: ostrzeżenie: deklaracja funkcji nie jest prototypem. Należy zadeklarować main()jak main(void)również.
Jens,
3
@Jens Dlaczego edytowałeś pytanie?
Wygląda
1
Właśnie ujawniłem ukrytą int. Jak to ma sens? Myślę, że chodzi o to, dlaczego int func();jest kompatybilny int func(arglist) { ... }.
Jens,
3
@MatsPetersson To jest źle. C99 5.1.2.2.1 wyraźnie zaprzecza twojemu twierdzeniu i stwierdza, że ​​wersja bez argumentu jest int main(void).
Jens

Odpowiedzi:

271

Wszystkie pozostałe odpowiedzi są poprawne, ale tylko w celu uzupełnienia

Funkcja jest deklarowana w następujący sposób:

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

return-type to typ zmiennej zwracany przez funkcję. Nie może to być typ tablicy ani typ funkcji. Jeśli nie podano, przyjmuje się, że int .

nazwa-funkcji to nazwa funkcji.

lista parametrów to lista parametrów oddzielonych przecinkami przez funkcję. Jeśli nie podano żadnych parametrów, funkcja nie przyjmuje żadnych i należy ją zdefiniować za pomocą pustego zestawu nawiasów lub słowa kluczowego void. Jeśli żaden typ zmiennej nie znajduje się przed zmienną na liście parametrów, przyjmuje się, że int . Tablice i funkcje nie są przekazywane do funkcji, ale są automatycznie konwertowane na wskaźniki. Jeśli lista jest zakończona wielokropkiem (, ...), to nie ma ustalonej liczby parametrów. Uwaga: nagłówka stdarg.h można użyć do uzyskania dostępu do argumentów podczas korzystania z elipsy.

I znowu ze względu na kompletność. Ze specyfikacji C11 6: 11: 6 (strona: 179)

Wykorzystanie declarators funkcyjnych z pustych nawiasów (nie prototyp formacie typu parametru declarators) jest przestarzałe cecha .

Krysznabhadra
źródło
2
„Jeśli żaden typ zmiennej nie znajduje się przed zmienną na liście parametrów, przyjmuje się, że int.” Widzę to w podanym przez ciebie linku, ale nie mogę go znaleźć w żadnym standardowym c89, c99 ... Czy możesz podać inne źródło?
godaygo
„Jeśli nie podano żadnych parametrów, to funkcja nie przyjmuje żadnych i powinna zostać zdefiniowana z pustym zestawem nawiasów” brzmi sprzecznie z odpowiedzią Tony'ego Lwa, ale właśnie dowiedziałem się, że odpowiedź Tony'ego Lwa jest trudna.
jakun
160

W C func()oznacza, że ​​możesz przekazać dowolną liczbę argumentów. Jeśli nie chcesz żadnych argumentów, musisz zadeklarować jako func(void). Typ przekazywany do funkcji, jeśli nie zostanie określony, przyjmuje wartość domyślną int.

Tony Lew
źródło
3
W rzeczywistości nie ma jednego typu domyślnie int. W rzeczywistości wszystkie argumenty są domyślnie ustawione na int (nawet typ zwracany). Wywoływanie jest całkowicie w porządku func(42,0x42);( wszystkie połączenia muszą używać formularza dwóch argumentów).
Jens,
1
W C jest dość powszechne, że nieokreślone typy domyślnie domyślnie int, na przykład kiedy deklarujesz zmienną: unsigned x; Jakiego typu jest zmienna x? Okazuje się, że jego niepodpisany int
bitek
4
Wolałbym powiedzieć, że niejawna int była powszechna w dawnych czasach. Na pewno już nie jest, aw C99 został usunięty z C.
Jens
2
Warto zauważyć, że ta odpowiedź dotyczy prototypów funkcji z pustymi listami parametrów, ale nie definicji funkcji z pustymi listami parametrów.
autystyczny
@mnemonicflow, który nie jest „nieokreślonym typem domyślnie int”; unsignedto po prostu inna nazwa dla tego samego typu co unsigned int.
hobbs
58

int func();to przestarzała deklaracja funkcji z dni, w których nie było standardu C, tj. dni K&R C (przed 1989 rokiem, w którym opublikowano pierwszy standard „ANSI C”).

Pamiętaj, że w K&R C nie było prototypów, a słowo kluczowe voidnie zostało jeszcze wynalezione. Wystarczyło powiedzieć kompilatorowi o typie zwracanej funkcji. Pusta lista parametrów w K&R C oznacza „nieokreśloną, ale stałą” liczbę argumentów. Naprawiono oznacza, że ​​musisz wywoływać funkcję z tą samą liczbą argumentów za każdym razem (w przeciwieństwie do funkcji variadic, np. printfGdzie liczba i typ mogą się różnić dla każdego połączenia).

Wiele kompilatorów zdiagnozuje ten konstrukt; w szczególności gcc -Wstrict-prototypespowie Ci, że „deklaracja funkcji nie jest prototypem”, która jest na miejscu, ponieważ wygląda jak prototyp (szczególnie jeśli jesteś zatruty przez C ++!), ale tak nie jest. Jest to deklaracja typu powrotu K&R C. w starym stylu.

Ogólna zasada: nigdy nie pozostawiaj pustej deklaracji listy parametrów pustej, użyj, int func(void)aby być konkretnym. Dzięki temu deklaracja typu zwrotu K&R staje się właściwym prototypem C89. Kompilatory są zadowolone, programiści są zadowoleni, statyczne kontrolery są zadowolone. Ci, którzy wprowadzają w błąd przez ^ W ^ Wfond z C ++, mogą się jednak denerwować, ponieważ muszą pisać dodatkowe znaki, gdy próbują ćwiczyć swoje umiejętności językowe :-)

Jens
źródło
1
Proszę nie odnosić tego do C ++. Jest zdrowy rozsądek , że lista pusta argumentem oznacza żadnych parametrów , a ludzie na świecie nie są zainteresowani, aby radzić sobie z błędów popełnionych przez K & R facetów około 40 lat temu. Ale eksperci komitetu wciąż ciągną za sobą wybitne wybory - do C99, C11.
pfalcon
1
@pfalcon Zdrowy rozsądek jest dość subiektywny. Jednym zdrowym rozsądkiem dla jednej osoby jest zwykłe szaleństwo wobec innych. Profesjonalni programiści C wiedzą, że puste listy parametrów wskazują na nieokreśloną, ale stałą liczbę argumentów. Zmiana tego prawdopodobnie złamałaby wiele implementacji. Obwinianie komitetów za unikanie cichych zmian polega na szczekaniu niewłaściwego drzewa, IMHO.
Jens
53
  • Pusta lista parametrów oznacza „wszelkie argumenty”, więc definicja jest niepoprawna.
  • Zakłada się, że brakujący typ to int.

Uważam jednak, że każda kompilacja, która ją przejdzie, nie ma skonfigurowanego poziomu ostrzeżeń / błędów, nie ma sensu dopuszczać faktycznego kodu.

rozwijać
źródło
30

To deklaracja i definicja funkcji stylu K&R . Z normy C99 (ISO / IEC 9899: TC3)

Sekcja 6.7.5.3 Deklaratory funkcji (w tym prototypy)

Lista identyfikatorów deklaruje tylko identyfikatory parametrów funkcji. Pusta lista w deklaratorze funkcji, która jest częścią definicji tej funkcji, określa, że ​​funkcja nie ma parametrów. Pusta lista w deklaratorze funkcji, która nie jest częścią definicji tej funkcji, określa, że ​​nie podano żadnych informacji o liczbie lub typach parametrów. (Jeśli oba typy funkcji są w „starym stylu”, typy parametrów nie są porównywane).

Sekcja 6.11.6 Deklaratory funkcji

Użycie funkcji deklarujących funkcje z pustymi nawiasami (nie deklarujących typu parametrów prototypowych) jest przestarzałą funkcją.

Sekcja 6.11.7 Definicje funkcji

Użycie definicji funkcji z oddzielnym identyfikatorem parametru i listą deklaracji (nie typem parametru prototypu i deklaratorem identyfikatora) jest przestarzałą funkcją.

Której stary styl oznacza K & R styl

Przykład:

Deklaracja: int old_style();

Definicja:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}
Lei Mou
źródło
16

C zakłada, intże nie podano żadnego typu dla typu zwracanej funkcji i listy parametrów . Tylko w przypadku tej zasady możliwe są następujące dziwne rzeczy.

Definicja funkcji wygląda następująco.

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

Jeśli piszesz prototyp

int func(int param);

W prototypie można określić tylko typ parametrów. Nazwa parametrów nie jest obowiązkowa. Więc

int func(int);

Również jeśli nie określisz typu parametru, ale nazwa intzostanie przyjęta jako typ.

int func(param);

Jeśli pójdziesz dalej, następujące działania również działają.

func();

Kompilator zakłada int func(), że piszesz func(). Ale nie umieszczaj func()w ciele funkcji. To będzie wywołanie funkcji

Shiplu Mokaddim
źródło
3
Pusta lista parametrów nie jest powiązana z niejawnym typem int. int func()nie jest niejawną formą int func(int).
John Bartholomew,
11

Jak stwierdzono @Krishnabhadra, wszystkie poprzednie odpowiedzi od innych użytkowników mają poprawną interpretację, a ja po prostu chcę dokonać bardziej szczegółowej analizy niektórych punktów.

W Old-C, podobnie jak w ANSI-C, „ beztypowy parametr formalny ”, weź wymiar rejestru pracy lub możliwości głębokości instrukcji (rejestry cienia lub cykl skumulowany instrukcji), w 8-bitowym MPU, będzie int16, w 16-bitowym MPU i tak będzie int16 i tak dalej, w przypadku architektury 64-bitowej może wybrać kompilację opcji takich jak: -m32.

Chociaż implementacja na wysokim poziomie wydaje się prostsza, przy przekazywaniu wielu parametrów praca programisty w kroku typu danych dimmencji sterującej staje się bardziej wymagająca.

W innych przypadkach, w przypadku niektórych architektur mikroprocesorów, spersonalizowane kompilatory ANSI wykorzystały niektóre z tych starych funkcji w celu zoptymalizowania wykorzystania kodu, zmuszając lokalizację tych „nietypowych parametrów formalnych” do pracy w rejestrze pracy lub poza nim, dziś otrzymujesz prawie tak samo z użyciem „lotnych” i „rejestrów”.

Należy jednak zauważyć, że najnowocześniejsze kompilatory nie rozróżniają dwóch typów deklaracji parametrów.

Przykłady kompilacji z gcc w systemie Linux:

main.c

main2.c

main3.c  
W każdym razie lokalna instrukcja prototypu jest bezużyteczna, ponieważ nie ma wywołania bez parametrów odniesienie do tego prototypu zostanie pominięte. Jeśli używasz systemu z „beztypowym parametrem formalnym”, w przypadku wywołania zewnętrznego przejdź do generowania deklaratywnego prototypowego typu danych.

Lubię to:

int myfunc(int param);
RTOSkit
źródło
5
Zadałem sobie trud zoptymalizowania obrazów, aby rozmiar w bajtach obrazów był możliwie najmniejszy. Myślę, że zdjęcia mogą szybko zobaczyć brak różnicy w generowanym kodzie. Testowałem kod, generując prawdziwą dokumentację tego, do czego się zgłosił, a nie tylko teoretykę na temat problemu. ale nie wszyscy widzą świat w ten sam sposób. Bardzo mi przykro, że moja próba dostarczenia społeczności więcej informacji, tak bardzo cię niepokoiła.
RTOSkit,
1
Jestem prawie pewien, że zawsze określony jest nieokreślony typ zwrotu int, który ogólnie jest albo wielkością rejestru pracy, albo 16 bitów, w zależności od tego, który jest mniejszy.
supercat
@ superupat +1 Idealne odliczenie, masz rację! Właśnie to omawiam powyżej, kompilator zaprojektowany domyślnie dla określonej architektury, zawsze odzwierciedla rozmiar rejestru roboczego danego procesora / MPU, więc w środowisku osadzonym istnieją różne strategie ponownego pisania na system operacyjny w czasie rzeczywistym, do warstwy kompilatora (stdint.h) lub do warstwy przenośności, która czyni przenośnym ten sam system operacyjny w wielu innych arquitectures i uzyskiwaną przez wyrównanie specyficznych typów CPU / MPU (int, long, long long itp.) z typami systemów ogólnych u8, u16, u32.
RTOSkit
@ superuper Bez typów kontrolnych oferowanych przez system operacyjny lub konkretny kompilator, musisz mieć trochę uwagi w czasie programowania, i sprawisz, że wszystkie „nietypowe przypisania” będą zgodne z projektem aplikacji, zanim będziesz zaskoczony znalezieniem „ 16bit int ”, a nie„ 32bit int ”.
RTOSkit
@RTOSkit: Twoja odpowiedź sugeruje, że domyślnym typem 8-bitowego procesora będzie Int8. Przypominam sobie kilka kompilatorów typu C dla architektury marki PICmicro, w których tak było, ale nie sądzę, aby cokolwiek przypominającego standard C pozwalało na inttyp, który nie byłby w stanie utrzymać wszystkich wartości w tym zakresie - 32767 do +32767 (nota -32768 nie jest wymagana), a także może przechowywać wszystkie charwartości (co oznacza, że ​​jeśli charjest 16 bitów, albo musi być podpisany, albo intmusi być większy).
supercat
5

Jeśli chodzi o typ parametru, odpowiedzi są już poprawne, ale jeśli chcesz usłyszeć je z kompilatora, możesz spróbować dodać kilka flag (flagi i tak prawie zawsze są dobrym pomysłem).

kompiluję twój program używając gcc foo.c -Wextra:

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

o dziwo -Wextranie łapie tego clang( -Wmissing-parameter-typez jakiegoś powodu nie rozpoznaje , być może historycznych wymienionych powyżej), ale -pedantic:

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

A w przypadku problemu z prototypem, jak wspomniano powyżej, int func()odnosi się do dowolnych parametrów, chyba że zostanie to dokładnie zdefiniowane jako takie, int func(void)które dałoby błędy zgodnie z oczekiwaniami:

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

lub clangjako:

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.
Żaden
źródło
3

Jeśli deklaracja funkcji nie ma parametrów, tzn. Jest pusta, przyjmuje nieokreśloną liczbę argumentów. Jeśli chcesz, aby nie przyjmował żadnych argumentów, zmień go na:

int func(void);
PP
źródło
1
„Jeśli prototyp funkcji nie ma parametrów” To nie jest prototyp funkcji, tylko deklaracja funkcji.
effeffe,
@effeffe naprawił to. Nie zdawałem sobie sprawy, że to pytanie zyska wiele uwagi;)
PP
3
Czy „prototyp funkcji” nie jest tylko alternatywnym (być może staroświeckim) wyrażeniem „deklaracji funkcji”?
Giorgio,
@Giorgio IMO, oba są poprawne. „prototyp funkcji” powinien pasować do „definicji funkcji”. Prawdopodobnie myślę, że efekt oznaczał deklarację w pytaniu, a to, co miałem na myśli, to moja odpowiedź.
PP
@Giorgio nie, deklaracja funkcji jest dokonywana na podstawie wartości typu zwracanego, identyfikatora i opcjonalnej listy parametrów; prototypy funkcji są deklaracjami funkcji z listą parametrów.
effeffe
0

Dlatego zazwyczaj radzę ludziom kompilować swój kod:

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

Te flagi wymuszają kilka rzeczy:

  • -Wississing-zmienne-deklaracje: Nie można zadeklarować funkcji niestatycznej bez uprzedniego uzyskania prototypu. To sprawia, że ​​bardziej prawdopodobne jest, że prototyp w pliku nagłówkowym pasuje do faktycznej definicji. Alternatywnie wymusza dodanie statycznego słowa kluczowego do funkcji, które nie muszą być widoczne publicznie.
  • -Wstrict-zmienne-deklaracje: Prototyp musi poprawnie wyświetlać argumenty.
  • - Definicja starego stylu: sama definicja funkcji musi również poprawnie zawierać listę argumentów.

Te flagi są również domyślnie używane w wielu projektach Open Source. Na przykład FreeBSD ma te flagi włączone podczas budowania z WARNS = 6 w twoim Makefile.

Ed Schouten
źródło