Sök…


Introduktion

I moderna C är rubrikfiler avgörande verktyg som måste utformas och användas korrekt. De tillåter kompilatorn att korscheckna oberoende sammanställda delar av ett program.

Rubriker förklarar typer, funktioner, makroer etc. som behövs av konsumenterna på en uppsättning anläggningar. Alla koder som använder någon av dessa faciliteter inkluderar rubriken. Alla koder som definierar dessa faciliteter inkluderar rubriken. Detta gör att kompilatorn kan kontrollera att användningen och definitionerna matchar.

Introduktion

Det finns ett antal riktlinjer att följa när du skapar och använder rubrikfiler i ett C-projekt:

  • Idemopotence

    Om en rubrikfil inkluderas flera gånger i en översättningsenhet (TU), bör den inte bryta builds.

  • Själv inneslutning

    Om du behöver de faciliteter som deklareras i en rubrikfil ska du inte behöva inkludera några andra rubriker uttryckligen.

  • minimalitet

    Du borde inte kunna ta bort någon information från en rubrik utan att orsaka att build byggs upp.

  • Inkludera vad du använder (IWYU)

    Av mer oro för C ++ än C, men ändå viktigt i C också. Om koden i en TU (kall det code.c ) direkt använder de funktioner som deklareras av en rubrik (kallar den "headerA.h" ), bör code.c #include "headerA.h" direkt, även om TU inkluderar en annan rubrik (kallar den "headerB.h" ) som händer för tillfället med "headerA.h" .

Ibland kan det finnas tillräckligt goda skäl att bryta en eller flera av dessa riktlinjer, men du båda måste vara medvetna om att du bryter regeln och vara medveten om konsekvenserna av att göra det innan du bryter mot den.

idempotent

Om en viss rubrikfil inkluderas mer än en gång i en översättningsenhet (TU), bör det inte finnas några kompilationsproblem. Detta kallas "idempotens"; dina rubriker ska vara idempotenta. Tänk hur svårt livet skulle vara om du var tvungen att se till att #include <stdio.h> bara inkluderades en gång.

Det finns två sätt att uppnå idempotens: rubrikvakter och #pragma once direktivet en #pragma once .

Huvudvakter

Huvudskydd är enkla och pålitliga och överensstämmer med C-standarden. De första icke-kommentarraderna i en rubrikfil ska ha formen:

#ifndef UNIQUE_ID_FOR_HEADER
#define UNIQUE_ID_FOR_HEADER

Den sista raden utan kommentarer bör vara #endif , eventuellt med en kommentar efter den:

#endif /* UNIQUE_ID_FOR_HEADER */

All operativ kod, inklusive andra #include direktiv, bör vara mellan dessa linjer.

Varje namn måste vara unikt. Ofta används ett HEADER_H_INCLUDED som HEADER_H_INCLUDED . En del äldre kod använder en symbol definierad som rubrikvakt (t.ex. #ifndef BUFSIZ i <stdio.h> ), men den är inte lika pålitlig som ett unikt namn.

Ett alternativ skulle vara att använda en genererad MD5-hash (eller annan) -hash som namn på rubrikskyddet. Du bör undvika att emulera de scheman som används av systemrubriker som ofta använder namn reserverade för implementeringen - namn som börjar med en understruk följt av antingen en annan understruk eller en stor bokstav.

#pragma once

Alternativt stöder vissa kompilatorer #pragma once direktivet en #pragma once som har samma effekt som de tre raderna som visas för rubrikskydd.

#pragma once

Kompilatorerna som stöder #pragma once inkluderar MS Visual Studio och GCC och Clang. Men om portabilitet är ett problem, är det bättre att använda rubriker eller använda båda. Moderna kompilatorer (de som stöder C89 eller senare) är skyldiga att ignorera, utan kommentarer, pragmas som de inte känner igen ("Något sådant pragma som inte erkänns av implementeringen ignoreras") men gamla versioner av GCC var inte så övergiven.

Själv inneslutning

Moderna rubriker ska vara fristående, vilket innebär att ett program som behöver använda de faciliteter som definieras av header.h kan inkludera den rubriken ( #include "header.h" ) och inte oroa dig för om andra rubriker måste inkluderas först.

Rekommendation: Headerfiler ska vara fristående.


Historiska regler

Historiskt sett har detta varit ett milt omtvistat ämne.

AT&T Indian Hill C Style and Coding Standards uttalade en gång till:

Rubrikfiler ska inte kapslas. Prologen för en rubrikfil bör därför beskriva vad andra rubriker behöver vara #include d för att rubriken ska fungera. I extrema fall, där ett stort antal rubrikfiler ska inkluderas i flera olika källfiler, är det acceptabelt att lägga alla vanliga #include s i en inkluderande fil.

Detta är antitesen om självinneslutning.

Moderna regler

Men sedan dess har åsikten tenderat i motsatt riktning. Om en källfil måste använda de anläggningar som deklareras av en header.h , bör programmeraren kunna skriva:

#include "header.h"

och (med förbehåll för att de korrekta sökvägarna är inställda på kommandoraden), kommer alla nödvändiga förutsatta rubriker att inkluderas av header.h utan att behöva ytterligare rubriker läggas till i källfilen.

Detta ger bättre modularitet för källkoden. Det skyddar också källan från "gissa varför den här rubriken lades till" conundrum som uppstår efter att koden har modifierats och hackats i ett decennium eller två.

NASA Goddard Space Flight Center (GSFC) -kodningsstandarder för C är en av de mer moderna standarderna - men är nu lite svårt att spåra. Den anger att rubriker ska vara fristående. Det ger också ett enkelt sätt att säkerställa att rubriker är fristående: implementeringsfilen för rubriken bör innehålla rubriken som den första rubriken. Om den inte är fristående kommer den koden inte att sammanställas.

Den grund som ges av GSFC inkluderar:

§2.1.1 Rubrik inkluderar skäl

Denna standard kräver att en enhets rubrik innehåller #include uttalanden för alla andra rubriker som krävs av enhetsrubriken. Genom att placera #include för enhetshuvudet först i enhetskroppen kan kompilatorn kontrollera att rubriken innehåller alla nödvändiga #include uttalanden.

En alternativ design som inte tillåts enligt denna standard tillåter inga #include uttalanden i rubriker; alla #includes görs i kroppsfilerna. Enhetshuvudfiler måste då innehålla #ifdef-uttalanden som kontrollerar att de nödvändiga rubrikerna ingår i rätt ordning.

En fördel med den alternativa designen är att listan #include inkludera i karosserifilen är exakt beroende-lista som behövs i en makefil, och denna lista kontrolleras av kompilatorn. Med standardkonstruktionen måste ett verktyg användas för att generera beroendelistan. Men alla grenens rekommenderade utvecklingsmiljöer tillhandahåller ett sådant verktyg.

En stor nackdel med den alternativa designen är att om en enhets nödvändiga rubriklista ändras måste varje fil som använder den enheten redigeras för att uppdatera uttalandelistan #include . Dessutom kan den önskade rubriklistan för en kompilatorbiblioteksenhet vara olika för olika mål.

En annan nackdel med den alternativa designen är att kompileringsbibliotekets rubrikfiler och andra tredjepartsfiler måste modifieras för att lägga till de obligatoriska #ifdef uttalandena.

Således innebär självinneslutning att:

  • Om en header header.h behöver en ny kapslad header extra.h , behöver du inte kontrollera varje källfil som använder header.h att se om du behöver lägga till extra.h .
  • Om en header header.h inte längre behöver inkludera en specifik header notneeded.h , behöver du inte kontrollera alla källfiler som använder header.h att se om du säkert kan ta bort notneeded.h (men se Inkludera det du använder .
  • Du behöver inte fastställa rätt sekvens för att inkludera de förutsatta rubrikerna (vilket kräver en topologisk sortering för att göra jobbet ordentligt).

Kontrollera självinneslutning

Se Länka mot ett statiskt bibliotek för ett skript chkhdr som kan användas för att testa idempotens och självinneslutning av en rubrikfil.

minimalitet

Rubriker är en avgörande mekanism för kontroll av konsistens, men de bör vara så små som möjligt. I synnerhet betyder det att en rubrik inte bör inkludera andra rubriker bara för att implementeringsfilen behöver de andra rubrikerna. En rubrik bör endast innehålla de rubriker som är nödvändiga för en konsument av de beskrivna tjänsterna.

Exempelvis bör ett projekthuvud inte inkludera <stdio.h> såvida inte ett av funktionsgränssnitten använder typen FILE * (eller en av de andra typerna som definieras enbart i <stdio.h> ). Om ett gränssnitt använder size_t är den minsta rubriken som räcker <stddef.h> . Självklart, om en annan rubrik som definierar size_t ingår, finns det inget behov att inkludera <stddef.h> också.

Om rubrikerna är minimala håller det också kompileringstiden till ett minimum.

Det är möjligt att ta fram rubriker vars enda syfte är att inkludera många andra rubriker. Dessa visar sig sällan vara en bra idé i det långa loppet eftersom få källfiler faktiskt behöver alla de faciliteter som beskrivs av alla rubriker. Till exempel kan en <standard-ch> utformas som innehåller alla standard C-rubriker - med försiktighet eftersom vissa rubriker inte alltid är närvarande. Men mycket få program använder faktiskt anläggningarna <locale.h> eller <tgmath.h> .

Inkludera vad du använder (IWYU)

Googles inkludera vad du använder- projektet, eller IWYU, säkerställer att källfiler innehåller alla rubriker som används i koden.

Anta att en source.c innehåller en rubrik arbitrary.h som i sin tur sammanfaller inkluderar freeloader.h , men källfilen använder också uttryckligen och oberoende anläggningarna från freeloader.h . Allt är bra att börja med. Sedan en dag arbitrary.h ändras så att dess klienter inte längre behöver freeloader.h . Plötsligt slutar source.c sammanställa - eftersom det inte uppfyller IWYU-kriterierna. Eftersom koden i source.c uttryckligen använde anläggningarna för freeloader.h , borde den ha inkluderat det den använder - det borde ha funnits ett uttryckligt #include "freeloader.h" i källan. ( Idempotency skulle ha säkerställt att det inte var något problem.)

IWYU-filosofin maximerar sannolikheten för att kod fortsätter att kompilera även med rimliga ändringar i gränssnitt. Det är tydligt att om din kod anropar en funktion som sedan tas bort från det publicerade gränssnittet kan inget förberedelsebelopp förhindra att ändringar behövs. Därför undviks ändringar av API: er när det är möjligt, och varför det finns avskrivningscykler över flera utgåvor, etc.

Detta är ett särskilt problem i C ++ eftersom standardrubriker får ta med varandra. file.cpp kan innehålla en header header1.h som på en plattform innehåller en annan header header2.h . file.cpp kan visa sig också använda faciliteterna i header2.h . Detta skulle initialt inte vara ett problem - koden skulle kompilera eftersom header1.h innehåller header2.h . På en annan plattform, eller en uppgradering av den aktuella plattformen, skulle header1.h kunna revideras så att den inte längre inkluderar header2.h , och sedan skulle file.cpp sluta kompilera som ett resultat.

IWYU skulle upptäcka problemet och rekommendera att header2.h inkluderas direkt i file.cpp . Detta skulle säkerställa att det fortsätter att sammanställas. Analoga överväganden gäller också C-kod.

Notation och diverse

C-standarden säger att det är väldigt liten skillnad mellan #include <header.h> och #include "header.h" .

[ #include <header.h> ] söker i en sekvens av implementeringsdefinerade platser efter en header identifierad unikt med den angivna sekvensen mellan < och > avgränsarna, och orsakar ersättningen av det direktivet med hela innehållet i rubriken. Hur platserna anges eller rubriken identifieras är implementeringsdefinierad.

[ #include "header.h" ] orsakar ersättning av direktivet genom hela innehållet i källfilen identifieras av den specificerade sekvensen mellan "…" avgränsare. Den namngivna källfilen söks efter på ett implementeringsdefinerat sätt. Om denna sökning inte stöds, eller om sökningen misslyckas, behandlas direktivet som om det läste [ #include <header.h> ] ...

Så den dubbla citerade formen kan se ut på fler ställen än den vinkelfäste formen. Standarden anger med exempel att standardrubrikerna ska inkluderas i vinkelfästen, även om kompilationen fungerar om du istället använder dubbla citat. På samma sätt använder standarder som POSIX det vinkelfäste formatet - och du borde också göra det. Boka rubriker med dubbla citat för rubriker som definieras av projektet. För externt definierade rubriker (inklusive rubriker från andra projekt som ditt projekt förlitar sig på) är noteringen av vinkelfästet lämpligast.

Observera att det bör finnas ett avstånd mellan #include och rubriken, även om kompilatorerna inte accepterar något utrymme där. Utrymmen är billiga.

Ett antal projekt använder en notation som:

#include <openssl/ssl.h>
#include <sys/stat.h>
#include <linux/kernel.h>

Du bör överväga om du vill använda den namnutrymmeskontrollen i ditt projekt (det är förmodligen en bra idé). Du bör undvika de namn som används av befintliga projekt (i synnerhet skulle både sys och linux vara dåliga val).

Om du använder detta bör din kod vara försiktig och konsekvent när du använder notationen.

Använd inte #include "../include/header.h" .

Rubrikfiler bör sällan om någonsin definiera variabler. Även om du kommer att hålla globala variabler till ett minimum, om du behöver en global variabel, kommer du att förklara den i en rubrik och definiera den i en lämplig källfil, och den källfilen kommer att innehålla rubriken för att korsa kontrollen av deklarationen och definitionen , och alla källfiler som använder variabeln kommer att använda rubriken för att deklarera den.

Sammanfattning: du kommer inte att deklarera globala variabler i en källfil - en källfil innehåller bara definitioner.

Rubrikfiler ska sällan deklara static funktioner, med det anmärkningsvärda undantaget för static inline som kommer att definieras i rubriker om funktionen behövs i mer än en källfil.

  • Källfiler definierar globala variabler och globala funktioner.
  • Källfiler förklarar inte att det finns globala variabler eller funktioner; de inkluderar rubriken som deklarerar variabeln eller funktionen.
  • Headerfiler förklarar global variabel och funktioner (och typer och annat stödmaterial).
  • Rubrikfiler definierar inte variabler eller funktioner förutom ( static ) inline .

Korsreferenser



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow