Conflicterende typen voor functie krijgen in C, waarom?

Ik gebruik de onderstaande code:

char dest[5];
char src[5] = "test";
printf("String: %s\n", do_something(dest, src));
char *do_something(char *dest, const char *src)
{
    return dest;
}

De implementatie van do_something is hier niet belangrijk.
Wanneer ik het bovenstaande probeer te compileren, krijg ik deze twee uitzonderingen:

fout: conflicterende typen voor ‘do_something’ (bij de printf-aanroep)
fout: vorige impliciete verklaring van ‘do_something’ was hier (bij de prototyperegel)

Waarom?


Antwoord 1, autoriteit 100%

Je probeert do_something aan te roepen voordat je het declareert. U moet een functieprototype toevoegen vóór uw printf-regel:

char* do_something(char*, const char*);

Of u moet de functiedefinitie boven de printf-regel verplaatsen. U kunt een functie niet gebruiken voordat deze is gedeclareerd.


Antwoord 2, autoriteit 18%

In de “klassieke” C-taal (C89/90) wanneer u een niet-gedeclareerde functie aanroept, neemt C aan dat deze een int retourneert en probeert ook de typen van zijn parameters af te leiden van de typen van de werkelijke argumenten (nee, er wordt niet vanuit gegaan dat het geen parameters heeft, zoals iemand eerder suggereerde).

In jouw specifieke voorbeeld zou de compiler kijken naar de aanroep do_something(dest, src) en impliciet een verklaring afleiden voor do_something. De laatste zou er als volgt uitzien

int do_something(char *, char *)

Maar later in de code verklaart u do_something expliciet als

char *do_something(char *, const char *)

Zoals je kunt zien, verschillen deze verklaringen van elkaar. Dit is wat de compiler niet leuk vindt.


Antwoord 3, autoriteit 9%

A C-functieverklaring achtergrond

In C werken functiedeclaraties niet zoals in andere talen: de C-compiler zoekt zelf niet heen en weer in het bestand om de declaratie van de functie te vinden vanaf de plaats waar u deze aanroept, en dat doet hij ook niet scan het bestand meerdere keren om de relaties te achterhalen: de compiler scant slechts vooruit in het bestand exact één keer, van boven naar beneden. Het verbinden van functieaanroepen met functiedeclaraties maakt deel uit van de taak van de linker en wordt pas gedaan nadat het bestand is gecompileerd tot ruwe montage-instructies.

Dit betekent dat wanneer de compiler door het bestand scant, de allereerste keer dat de compiler de naam van een functie tegenkomt, een van de twee dingen het geval moet zijn: ofwel ziet hij de functiedeclaratie zelf, in welk geval de compiler weet precies wat de functie is en welke typen het als argumenten nodig heeft en welke typen het retourneert of het is een aanroep van de functie, en de compiler moet raden hoe de functie uiteindelijk zal worden gedeclareerd.

(Er is een derde optie, waarbij de naam wordt gebruikt in een functie-prototype, maar die laten we voorlopig buiten beschouwing, want als je dit probleem in de eerste plaats ziet, gebruik je waarschijnlijk geen prototypes.)

Geschiedenisles

In de begindagen van C was het feit dat de compiler typen moest raden niet echt een probleem: alle typen waren min of meer hetzelfde – vrijwel alles was een int of een pointer, en ze waren even groot. (In feite waren er in B, de taal die aan C voorafging, helemaal geen typen; alles was gewoon een int of pointer en het type werd uitsluitend bepaald door hoe je het gebruikte!) Dus de compiler kon het gedrag van elke willekeurige functie alleen gebaseerd op het aantal parameters dat is doorgegeven: als je twee parameters zou doorgeven, zou de compiler twee dingen op de aanroepstack duwen, en vermoedelijk zou de aangeroepene twee argumenten hebben gedeclareerd, en dat zou allemaal op één lijn liggen. Als je slechts één parameter hebt doorgegeven, maar de functie verwachtte er twee, zou het nog steeds een beetje werken, en het tweede argument zou gewoon worden genegeerd/vuilnis. Als je drie parameters hebt doorgegeven en de functie er twee verwacht, zou het ook nog steeds een beetje werken, en de derde parameter zou worden genegeerd en vertrapt door de lokale variabelen van de functie. (Sommige oude C-code verwacht nog steeds dat deze regels met niet-overeenkomende argumenten ook zullen werken.)

Maar als de compiler je alles aan iets laat doorgeven, is dat niet echt een goede manier om een ​​programmeertaal te ontwerpen. Het werkte goed in de begindagen omdat de vroege C-programmeurs meestal tovenaars waren, en ze wisten niet het verkeerde type door te geven aan functies, en zelfs als ze de typen verkeerd hadden, waren er altijd tools zoals lint dat zou een diepere dubbele controle van uw C-code kunnen doen en u voor dergelijke dingen kunnen waarschuwen.

Vooruitspoelen naar vandaag, en we zitten niet helemaal in hetzelfde schuitje. C is opgegroeid, en veel mensen programmeren erin die geen tovenaars zijn, en om hen tegemoet te komen (en om iedereen die toch regelmatig lint gebruikten tegemoet te komen), hebben de compilers veel van de mogelijkheden die voorheen deel uitmaakten van lint vooral het deel waar ze uw code controleren om er zeker van te zijn dat deze typeveilig is. Vroege C-compilers lieten je int foo = "hello"; schrijven en het zou gewoon de aanwijzer toewijzen aan het gehele getal, en het was aan jou om ervoor te zorgen dat je geen domme dingen deed. Moderne C-compilers klagen luid als je je typen verkeerd hebt, en dat is maar goed ook.

Typeconflicten

Dus wat heeft dit allemaal te maken met de mysterieuze fout van het conflicterende type op de regel van de functiedeclaratie? Zoals ik hierboven al zei, moeten C-compilers nog steeds weten of raden wat een naam betekent wanneer ze die naam voor het eerst zien terwijl ze door het bestand bladeren: ze kunnen weet wat het betekent als het een daadwerkelijke functiedeclaratie zelf is (of een functie “prototype”, daarover binnenkort meer), maar als het slechts een aanroep van de functie is, moeten ze raden. En helaas is de gok vaak verkeerd.

Toen de compiler je aanroep naar do_something() zag, keek hij hoe deze werd aangeroepen en concludeerde hij dat do_something() uiteindelijk als volgt zou worden gedeclareerd:

int do_something(char arg1[], char arg2[])
{
    ...
}

Waarom concludeerde het dat? Omdat je het zo noemde! (Sommige C-compilers kunnen concluderen dat het int do_something(int arg1, int arg2) was, of gewoon int do_something(...), die beide zelfs verder van wat je wilt, maar het belangrijkste punt is dat ongeacht hoe de compiler de typen raadt, hij ze anders raadt dan wat je eigenlijke functie gebruikt.)

Later, terwijl de compiler het bestand verder scant, ziet hij uw werkelijke-declaratie van char *do_something(char *, char *). Die functiedeclaratie komt niet eens in de buurt van de declaratie die de compiler heeft geraden, wat betekent dat de regel waar de compiler de aanroep heeft gecompileerd verkeerd is gecompileerd en dat het programma gewoon niet gaat werken. Het drukt dus terecht een foutmelding af die u vertelt dat uw code niet gaat werken zoals geschreven.

Je vraagt ​​je misschien af: “Waarom wordt ervan uitgegaan dat ik een int terugstuur?” Wel, het veronderstelt dat type omdat er geen informatie over het tegendeel is: printf() kan elk type in zijn variabele argumenten opnemen, dus zonder een beter antwoord, int is net zo’n goede gok als alle andere. (Veel vroege C-compilers gingen altijd uit van int voor elk niet-gespecificeerd type, en namen aan dat je ... bedoelde voor de argumenten voor elke functie gedeclareerd f() niet void daarom raden veel moderne codestandaarden aan om altijd void in te voeren voor de argumenten als die er echt niet zouden moeten zijn.)

De oplossing

Er zijn twee veelvoorkomende oplossingen voor de functiedeclaratiefout.

De eerste oplossing, die door veel andere antwoorden hier wordt aanbevolen, is om een ​​prototype in de broncode boven te plaatsen waar de functie voor het eerst wordt aangeroepen. Een prototype lijkt precies op de declaratie van de functie, maar heeft een puntkomma waar de hoofdtekst zou moeten staan:

char *do_something(char *dest, const char *src);

Door het prototype eerst te plaatsen, weet de compiler hoe de functie er uiteindelijk uit zal zien, zodat hij niet hoeft te raden. Volgens afspraak plaatsen programmeurs prototypes vaak bovenaan het bestand, net onder de #include-instructies, om ervoor te zorgen dat ze altijd worden gedefinieerd voordat ze mogelijk worden gebruikt.

De andere oplossing, die ook in een aantal real-world code voorkomt, is om je functies eenvoudig opnieuw te ordenen, zodat de functiedeclaraties altijd voor zijn dat ze aanroept! Je zou de hele functie char *do_something(char *dest, const char *src) { ... } boven de eerste aanroep ervan kunnen verplaatsen, en de compiler zou dan precies weten hoe de functie eruitziet en hoefde niet te raden.

In de praktijk gebruiken de meeste mensen functie-prototypes, omdat je ook functie-prototypes kunt nemen en deze naar header-bestanden (.h) kunt verplaatsen, zodat de code in andere .c bestanden kunnen die functies aanroepen. Maar beide oplossingen werken, en veel codebases gebruiken beide.

C99 en C11

Het is handig om op te merken dat de regels iets anders zijn in de nieuwere versies van de C-standaard. In de eerdere versies (C89 en K&R) zou de compiler echt de typen raden tijdens het aanroepen van een functie (en compilers uit het K&R-tijdperk zouden je vaak niet eens waarschuwen als ze het mis hadden ). C99 en C11 vereisen beide dat de functiedeclaratie/prototype voorafgaat aan de eerste aanroep, en het is een fout als dit niet het geval is. Maar veel moderne C-compilers – voornamelijk voor achterwaartse compatibiliteit met eerdere code – zullen alleen waarschuwingen voor een ontbrekend prototype en het niet als een fout beschouwen.


Antwoord 4, autoriteit 6%

Je hebt het niet aangegeven voordat je het gebruikte.

Je hebt zoiets nodig

char *do_something(char *, const char *);

vóór de printf.

Voorbeeld:

#include <stdio.h>
char *do_something(char *, const char *);
char dest[5];
char src[5] = "test";
int main ()
{
printf("String: %s\n", do_something(dest, src));
 return 0;
}
char *do_something(char *dest, const char *src)
{
return dest;
}

U kunt ook de hele functie do_something voor de printf plaatsen.


Antwoord 5, autoriteit 4%

U moet de functie declareren voordat u deze gebruikt. Als de functienaam vóór zijn declaratie verschijnt, zal de C-compiler bepaalde regels volgen en de declaratie zelf maken. Als het fout is, krijg je die fout.

Je hebt twee opties: (1) definieer het voordat je het gebruikt, of (2) gebruik forward declaratie zonder implementatie. Bijvoorbeeld:

char *do_something(char *dest, const char *src);

Let op de puntkomma aan het einde.


Antwoord 6, autoriteit 3%

C-gebod #3:

K&R #3 Thou shalt always prototype your functions or else the C compiler will extract vengence. 

http://www. ee.ryerson.ca:8080/~elf/hack/God.vs.K+R.html


Antwoord 7, autoriteit 2%

Als je geen prototype voor de functie geeft voordat je deze gebruikt, gaat C ervan uit dat er een willekeurig aantal parameters voor nodig is en retourneert een int. Dus wanneer u voor het eerst do_something probeert te gebruiken, is dat het type functie waarnaar de compiler op zoek is. Dit zou een waarschuwing moeten opleveren over een “impliciete functiedeclaratie”.

Dus in jouw geval, wanneer je de functie later daadwerkelijk declareert, staat C geen functieoverbelasting toe, dus het wordt pissig omdat je twee functies hebt gedeclareerd met verschillende prototypes maar met dezelfde naam.

p>

Kort antwoord: declareer de functie voordat je deze probeert te gebruiken.


Antwoord 8, autoriteit 2%

Kijk nog een keer:

char dest[5];
char src[5] = "test";
printf("String: %s\n", do_something(dest, src));

Focus op deze regel:

printf("String: %s\n", do_something(dest, src));

Je kunt duidelijk zien dat de functie do_something niet is gedeclareerd!

Als je wat verder kijkt,

printf("String: %s\n", do_something(dest, src));
char *do_something(char *dest, const char *src)
{
return dest;
}

je zult zien dat je de functie declareert nadat je het gebruikt.

Je moet dit onderdeel aanpassen met deze code:

char *do_something(char *dest, const char *src)
{
return dest;
}
printf("String: %s\n", do_something(dest, src));

Proost 😉


Antwoord 9

Dit gebeurt vaak wanneer u een c-functiedefinitie wijzigt en vergeet de corresponderende headerdefinitie bij te werken.


Antwoord 10

Zorg ervoor dat typen in de functiedeclaratie als eerste worden gedeclareerd.

/* start of the header file */

.

.

.

struct intr_frame{...}; //must be first!

.

.

.

void kill (struct intr_frame *);

.

.

.

/* end of the header file */

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Other episodes