Zoeken…


Invoering

De C-taal is traditioneel een gecompileerde taal (in tegenstelling tot geïnterpreteerd). De C-standaard definieert vertaalfasen en het product van het toepassen ervan is een programmabeeld (of gecompileerd programma). In worden de fasen vermeld in §5.1.1.2.

Opmerkingen

Bestandsnaamextensie Beschrijving
.c Bron bestand. Bevat meestal definities en code.
.h Koptekstbestand. Bevat meestal aangiften.
.o Objectbestand. Gecompileerde code in machinetaal.
.obj Alternatieve extensie voor objectbestanden.
.a Bibliotheekbestand. Pakket objectbestanden.
.dll Dynamic-Link-bibliotheek op Windows.
.so Gedeeld object (bibliotheek) op veel Unix-achtige systemen.
.dylib Dynamic-Link Library op OSX (Unix-variant).
.exe , .com Windows uitvoerbaar bestand. Gevormd door objectbestanden en bibliotheekbestanden te koppelen. In Unix-achtige systemen is er geen speciale bestandsnaamextensie voor uitvoerbaar bestand.
POSIX c99 compiler vlaggen Beschrijving
-o filename Naam uitvoerbestand, bijv. ( bin/program.exe , program )
-I directory zoeken naar headers in direrctory .
-D name definiëren macro name
-L directory zoek naar bibliotheken in de directory .
-l name link bibliotheek libname .

Compilers op POSIX-platforms (Linux, mainframes, Mac) accepteren deze opties meestal, zelfs als ze geen c99 worden genoemd.

GCC (GNU Compiler Collection) vlaggen Beschrijving
-Wall Hiermee worden alle waarschuwingsberichten die algemeen worden geaccepteerd, nuttig geacht.
-Wextra Maakt meer waarschuwingsberichten mogelijk, kan te luidruchtig zijn.
-pedantic Waarschuwingen afdwingen waar code de gekozen norm schendt.
-Wconversion Schakel waarschuwingen in voor impliciete conversie, wees voorzichtig.
-c Compileert bronbestanden zonder te linken.
-v Druk compilatie-info af.
  • gcc accepteert de POSIX-vlaggen plus vele andere.
  • Veel andere compilers op POSIX-platforms ( clang , leverancierspecifieke compilers) gebruiken ook de vlaggen die hierboven worden vermeld.
  • Zie ook GCC aanroepen voor veel meer opties.
TCC (Tiny C Compiler) vlaggen Beschrijving
-Wimplicit-function-declaration Waarschuw voor impliciete functieverklaring.
-Wunsupported Waarschuw voor niet-ondersteunde GCC-functies die door TCC worden genegeerd.
-Wwrite-strings Maak stringconstanten van het type const char * in plaats van char *.
-Werror Compilatie afbreken als waarschuwingen worden gegeven.
-Wall Activeer alle waarschuwingen, behalve -Werror , -Wunusupported en -Wwrite strings .

De linker

De taak van de linker is het koppelen van een aantal objectbestanden ( .o bestanden) in een binair uitvoerbaar bestand. Het koppelingsproces omvat voornamelijk het omzetten van symbolische adressen in numerieke adressen . Het resultaat van het koppelingsproces is normaal een uitvoerbaar programma.

Tijdens het koppelingsproces haalt de linker alle objectmodules op die op de opdrachtregel zijn opgegeven, voegt een systeemspecifieke opstartcode toe en probeert alle externe verwijzingen in de objectmodule op te lossen met externe definities in andere objectbestanden (objectbestanden) kan rechtstreeks op de opdrachtregel worden opgegeven of kan impliciet via bibliotheken worden toegevoegd). Het zal dan toewijzen belasting adressen voor het object bestanden, dat wil zeggen, het wordt aangegeven waar de code en data zal eindigen in de adresruimte van het voltooide programma. Als het eenmaal de laadadressen heeft, kan het alle symbolische adressen in de objectcode vervangen door "echte", numerieke adressen in de adresruimte van het doel. Het programma is nu klaar om te worden uitgevoerd.

Dit omvat zowel de objectbestanden die de compiler heeft gemaakt op basis van uw broncodebestanden als objectbestanden die vooraf voor u zijn gecompileerd en zijn verzameld in bibliotheekbestanden. Deze bestanden hebben namen die eindigen op .a of .so , en u hoeft ze normaal gesproken niet te weten, omdat de linker weet waar de meeste zich bevinden en ze indien nodig automatisch koppelen.

Impliciete aanroep van de linker

Net als de pre-processor is de linker een afzonderlijk programma, vaak ld (maar Linux gebruikt bijvoorbeeld collect2 ). Evenals de pre-processor wordt de linker automatisch voor u aangeroepen wanneer u de compiler gebruikt. De normale manier om de linker te gebruiken is dus als volgt:

% gcc foo.o bar.o baz.o -o myprog

Deze regel vertelt de compiler om drie objectbestanden ( foo.o , bar.o en baz.o ) te baz.o in een binair uitvoerbaar bestand met de naam myprog . Nu heb je een bestand genaamd myprog dat je kunt uitvoeren en dat hopelijk iets myprog en / of nuttigs zal doen.

Expliciete aanroep van de linker

Het is mogelijk om de linker direct op te roepen, maar dit is zelden aan te raden, en is meestal zeer platform-specifiek. Dat wil zeggen, opties die op Linux werken, werken niet noodzakelijkerwijs op Solaris, AIX, macOS, Windows en op dezelfde manier voor elk ander platform. Als u met GCC werkt, kunt u gcc -v om te zien wat namens u wordt uitgevoerd.

Opties voor de linker

De linker neemt ook enkele argumenten om zijn gedrag te wijzigen. De volgende opdracht zou gcc vertellen om foo.o en bar.o te koppelen, maar ook de bibliotheek ncurses .

% gcc foo.o bar.o -o foo -lncurses

Dit is eigenlijk (min of meer) equivalent aan

% gcc foo.o bar.o /usr/lib/libncurses.so -o foo

(hoewel libncurses.so zou kunnen zijn libncurses.a , dat is gewoon een archief gemaakt met ar ). Merk op dat u de bibliotheken (op padnaam of via - -lname ) moet vermelden na de objectbestanden. Bij statische bibliotheken is de volgorde waarin ze zijn gespecificeerd van belang; bij gedeelde bibliotheken maakt de volgorde vaak niet uit.

Merk op dat op veel systemen, als u wiskundige functies gebruikt (van <math.h> ), u -lm moet opgeven om de wiskundebibliotheek te laden - maar Mac OS X en macOS Sierra hebben dit niet nodig. Er zijn andere bibliotheken die afzonderlijke bibliotheken zijn op Linux en andere Unix-systemen, maar niet op macOS - POSIX-threads en POSIX realtime, en netwerkbibliotheken zijn voorbeelden. Bijgevolg varieert het koppelingsproces tussen platforms.

Andere compilatie-opties

Dit is alles wat u moet weten om te beginnen met het compileren van uw eigen C-programma's. Over het algemeen raden we u ook aan om de opdrachtregeloptie -Wall te gebruiken:

% gcc -Wall -c foo.cc

De optie -Wall zorgt ervoor dat de compiler u waarschuwt voor legale maar dubieuze -Wall en helpt u heel veel bugs te vangen.

Als u wilt dat de compiler meer waarschuwingen naar u gooit (inclusief variabelen die zijn gedeclareerd maar niet worden gebruikt, vergeten een waarde terug te geven, enz.), Kunt u deze set opties gebruiken, omdat -Wall , ondanks de naam, niet draait alle mogelijke waarschuwingen over:

% gcc -Wall -Wextra -Wfloat-equal -Wundef -Wcast-align -Wwrite-strings -Wlogical-op \
>     -Wmissing-declarations -Wredundant-decls -Wshadow …

Merk op dat clang heeft een optie -Weverything die echt wordt ingeschakeld alle waarschuwingen in clang .

Bestand types

Voor het compileren van C-programma's moet u met vijf soorten bestanden werken:

  1. Bronbestanden : deze bestanden bevatten functiedefinities en hebben namen die volgens conventie op .c eindigen. Opmerking: .cc en .cpp zijn C ++ -bestanden; geen C-bestanden.
    bijv. foo.c

  2. Koptekstbestanden : deze bestanden bevatten functieprototypes en verschillende pre-processorafschriften (zie hieronder). Ze worden gebruikt om broncodebestanden toegang te geven tot extern gedefinieerde functies. Koptekstbestanden eindigen volgens afspraak op .h .
    bijv. foo.h

  3. Objectbestanden : deze bestanden worden geproduceerd als de uitvoer van de compiler. Ze bestaan uit functiedefinities in binaire vorm, maar ze kunnen niet op zichzelf worden uitgevoerd. Objectbestanden eindigen volgens de conventie in .o , hoewel ze op sommige besturingssystemen (bijv. Windows, MS-DOS) vaak eindigen op .obj .
    bijv. foo.o foo.obj

  4. Binaire uitvoerbare bestanden : deze worden geproduceerd als de uitvoer van een programma dat een "linker" wordt genoemd. De linker verbindt een aantal objectbestanden om een binair bestand te produceren dat direct kan worden uitgevoerd. Binaire uitvoerbare bestanden hebben geen speciaal achtervoegsel op Unix-besturingssystemen, hoewel ze meestal eindigen op .exe op Windows.
    bijv. foo foo.exe

  5. Bibliotheken : een bibliotheek is een gecompileerd binair bestand, maar is op zichzelf geen uitvoerbaar bestand (er is dus geen main() functie in een bibliotheek). Een bibliotheek bevat functies die door meer dan één programma kunnen worden gebruikt. Een bibliotheek moet worden geleverd met header-bestanden die prototypes bevatten voor alle functies in de bibliotheek; naar deze header-bestanden moet worden verwezen (bijvoorbeeld; #include <library.h> ) in elk bronbestand dat de bibliotheek gebruikt. De linker moet dan worden doorverwezen naar de bibliotheek, zodat het programma met succes kan worden gecompileerd. Er zijn twee soorten bibliotheken: statisch en dynamisch.

    • Statische bibliotheek : een statische bibliotheek ( .a bestanden voor POSIX-systemen en .lib bestanden voor Windows - niet te verwarren met DLL-importbibliotheekbestanden , die ook de .lib extensie gebruiken) is statisch ingebouwd in het programma. Statische bibliotheken hebben het voordeel dat het programma precies weet welke versie van een bibliotheek wordt gebruikt. Anderzijds zijn de uitvoerbare groottes groter omdat alle gebruikte bibliotheekfuncties zijn inbegrepen.
      bijv. libfoo.a foo.lib
    • Dynamische bibliotheek : een dynamische bibliotheek ( .so bestanden voor de meeste POSIX-systemen, .dylib voor OSX en .dll bestanden voor Windows) wordt tijdens runtime dynamisch gekoppeld door het programma. Dit worden soms ook gedeelde bibliotheken genoemd omdat één bibliotheekafbeelding door veel programma's kan worden gedeeld. Dynamische bibliotheken hebben het voordeel dat ze minder schijfruimte innemen als meer dan één toepassing de bibliotheek gebruikt. Ze staan ook bibliotheekupdates toe (bugfixes) zonder uitvoerbare bestanden opnieuw te moeten opbouwen.
      bijv. foo.so foo.dylib foo.dll

De voorverwerker

Voordat de C-compiler begint met het compileren van een broncodebestand, wordt het bestand verwerkt in een voorbewerkingsfase. Deze fase kan worden uitgevoerd door een afzonderlijk programma of volledig worden geïntegreerd in één uitvoerbaar bestand. In elk geval wordt het automatisch aangeroepen door de compiler voordat de eigenlijke compilatie begint. De voorbewerkingsfase converteert uw broncode naar een andere broncode of vertaaleenheid door tekstuele vervangingen toe te passen. Je kunt het zien als een "aangepaste" of "uitgebreide" broncode. Die uitgebreide bron kan bestaan als een echt bestand in het bestandssysteem, of het kan slechts kort in het geheugen worden opgeslagen voordat het verder wordt verwerkt.

Preprocessor-opdrachten beginnen met het hekje ("#"). Er zijn verschillende preprocessoropdrachten; twee van de belangrijkste zijn:

  1. Definieert :

    #define wordt voornamelijk gebruikt om constanten te definiëren. Bijvoorbeeld,

    #define BIGNUM 1000000
    int a = BIGNUM; 
    

    wordt

    int a = 1000000;
    

    #define wordt op deze manier gebruikt om te voorkomen dat op een aantal verschillende plaatsen in een broncodebestand expliciet een constante waarde moet worden weggeschreven. Dit is belangrijk voor het geval u later de constante waarde moet wijzigen; het is veel minder gevoelig voor fouten om het in de #define eenmaal te wijzigen dan om het op meerdere plaatsen te moeten wijzigen, verspreid over de code.

    Omdat #define alleen geavanceerd zoeken en vervangen doet, kunt u ook macro's declareren. Bijvoorbeeld:

    #define ISTRUE(stm) do{stm = stm ? 1 : 0;}while(0)
    // in the function:
    a = x;
    ISTRUE(a);
    

    wordt:

    // in the function:
    a = x;
    do {
        a = a ? 1 : 0;
    } while(0);
    

    Bij de eerste benadering is dit effect ongeveer hetzelfde als bij inline-functies, maar de preprocessor biedt geen typecontrole voor #define macro's. Het is bekend dat dit foutgevoelig is en het gebruik ervan vereist grote voorzichtigheid.

    Merk ook op dat de preprocessor ook opmerkingen zou vervangen door spaties, zoals hieronder wordt uitgelegd.

  2. Omvat :

    #include wordt gebruikt om toegang te krijgen tot functiedefinities die buiten een broncodebestand zijn gedefinieerd. Bijvoorbeeld:

     #include <stdio.h> 
    

    zorgt ervoor dat de preprocessor de inhoud van <stdio.h> in het broncodebestand op de locatie van de #include instructie plakt voordat deze wordt gecompileerd. #include wordt bijna altijd gebruikt om header-bestanden op te nemen. Dit zijn bestanden die hoofdzakelijk functieverklaringen en #include #define instructies bevatten. In dit geval gebruiken we #include om functies zoals printf en scanf te kunnen gebruiken, waarvan de verklaringen zich in het bestand stdio.h . C-compilers staan u niet toe een functie te gebruiken tenzij deze eerder in dat bestand is aangegeven of gedefinieerd; #include statements zijn dus de manier om eerder geschreven code in uw C-programma's opnieuw te gebruiken.

  3. Logische bewerkingen :

    #if defined A || defined B
    variable = another_variable + 1;
    #else
    variable = another_variable * 2;
    #endif
    

    zal worden gewijzigd in:

    variable = another_variable + 1;
    

    als A of B eerder in het project zijn gedefinieerd. Als dit niet het geval is, doet de preprocessor dit uiteraard:

    variable = another_variable * 2;
    

    Dit wordt vaak gebruikt voor code, die op verschillende systemen draait of op verschillende compilers compileert. Aangezien er globale definities zijn, die specifiek zijn voor de compiler / systeem, kun je deze testen en laat de compiler altijd gewoon de code gebruiken die hij zeker zal compileren.

  4. Comments

    De Preprocessor vervangt alle opmerkingen in het bronbestand door enkele spaties. Opmerkingen worden aangegeven met // tot het einde van de regel, of een combinatie van haakjes openen /* en sluiten */ opmerkingen.

De compiler

Nadat de C-processor alle kopbestanden heeft opgenomen en alle macro's heeft uitgebreid, kan de compiler het programma compileren. Het doet dit door de C-broncode om te zetten in een objectcodebestand, een bestand dat eindigt op .o dat de binaire versie van de broncode bevat. Objectcode is echter niet direct uitvoerbaar. Om een uitvoerbaar bestand te maken, moet u ook code toevoegen voor alle bibliotheekfuncties die #include omvatten d in het bestand (dit is niet hetzelfde als het opnemen van de verklaringen, wat #include doet). Dit is de taak van de linker .

Over het algemeen hangt de exacte volgorde van het oproepen van een C-compiler sterk af van het systeem dat u gebruikt. Hier gebruiken we de GCC-compiler, hoewel er nog veel meer compilers bestaan:

% gcc -Wall -c foo.c

% is de opdrachtprompt van het besturingssysteem. Dit vertelt de compiler om de pre-processor op het bestand foo.c en deze vervolgens in het objectcodebestand foo.o . De optie -c betekent dat het broncodebestand in een objectbestand wordt gecompileerd, maar dat de linker niet wordt aangeroepen. Deze optie -c is beschikbaar op POSIX-systemen, zoals Linux of macOS; andere systemen kunnen verschillende syntaxis gebruiken.

Als uw hele programma zich in één broncodebestand bevindt, kunt u dit in plaats daarvan doen:

% gcc -Wall foo.c -o foo

Dit vertelt de compiler om de pre-processor op foo.c , te compileren en vervolgens te koppelen om een uitvoerbaar bestand genaamd foo . De optie -o geeft aan dat het volgende woord op de regel de naam is van het binaire uitvoerbare bestand (programma). Als u de -o niet opgeeft (als u gewoon gcc foo.c ), wordt het uitvoerbare bestand om historische redenen a.out genoemd.

Over het algemeen neemt de compiler vier stappen bij het converteren van een .c bestand naar een uitvoerbaar bestand:

  1. voorbewerking - breidt tekst #include richtlijnen en #define macro's in uw .c bestand
  2. compilatie - converteert het programma naar assemblage (u kunt de compiler bij deze stap stoppen door de optie -S toe te voegen)
  3. assemblage - converteert de assemblage naar machinecode
  4. koppeling - koppelt de objectcode aan externe bibliotheken om een uitvoerbaar bestand te maken

Merk ook op dat de naam van de compiler die we gebruiken GCC is, wat staat voor zowel "GNU C compiler" als "GNU compiler collection", afhankelijk van de context. Andere C-compilers bestaan. Voor Unix-achtige besturingssystemen hebben veel van hen de naam cc , voor "C compiler", wat vaak een symbolische link is naar een andere compiler. Op Linux-systemen is cc vaak een alias voor GCC. Op macOS of OS-X wijst dit op clang.

De POSIX-standaard verplicht momenteel c99 als de naam van een C-compiler - het ondersteunt standaard de C99-standaard. Eerdere versies van POSIX verplichtten c89 als compiler. POSIX verplicht ook dat deze compiler de opties -c en -o begrijpt die we hierboven hebben gebruikt.


Opmerking: De optie -Wall in beide gcc voorbeelden vertelt de compiler om waarschuwingen af te drukken over dubieuze constructies, wat ten zeerste wordt aanbevolen. Het is ook een goed idee om andere waarschuwingsopties toe te voegen, bijvoorbeeld -Wextra .

De vertaalfasen

Vanaf de C 2011-norm, vermeld in §5.1.1.2 Vertaalfasen , wordt de vertaling van broncode naar programmabeeld (bijvoorbeeld het uitvoerbare bestand) vermeld in 8 geordende stappen.

  1. De invoer van het bronbestand wordt toegewezen aan de brontekenset (indien nodig). Trigrafieën worden in deze stap vervangen.
  2. Vervolgregels (regels die eindigen op \ ) worden gesplitst met de volgende regel.
  3. De broncode wordt ontleed in witruimte en voorverwerkingstokens.
  4. De preprocessor wordt toegepast, die richtlijnen uitvoert, macro's uitbreidt en pragma's toepast. Elk bronbestand aangetrokken door #include ondergaat vertaalfase 1 tot en met 4 (recursief indien nodig). Alle preprocessor-gerelateerde richtlijnen worden vervolgens verwijderd.
  5. Waarden van brontekenset in tekenconstanten en tekenreeksliteralen worden toegewezen aan de uitvoeringstekenset.
  6. Stringliteralen naast elkaar worden aaneengeschakeld.
  7. De broncode wordt ontleed in tokens, die de vertaaleenheid vormen.
  8. Externe referenties worden opgelost en het programmabeeld wordt gevormd.

Een implementatie van een C-compiler kan verschillende stappen samen combineren, maar de resulterende afbeelding moet zich nog gedragen alsof de bovenstaande stappen afzonderlijk in de hierboven vermelde volgorde hadden plaatsgevonden.



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow