C Language
Maak en neem koptekstbestanden op
Zoeken…
Invoering
In moderne C zijn header-bestanden cruciale tools die correct moeten worden ontworpen en gebruikt. Hiermee kan de compiler onafhankelijk gecompileerde delen van een programma controleren.
Headers verklaren types, functies, macro's enz. Die nodig zijn voor de consumenten van een reeks voorzieningen. Alle code die een van deze voorzieningen gebruikt, bevat de koptekst. Alle code die deze voorzieningen definieert, bevat de koptekst. Hiermee kan de compiler controleren of het gebruik en de definities overeenkomen.
Invoering
Er zijn een aantal richtlijnen die moeten worden gevolgd bij het maken en gebruiken van header-bestanden in een C-project:
Idemopotence
Als een koptekstbestand meerdere keren wordt opgenomen in een vertaaleenheid (TU), mag dit geen builds verbreken.
Self-containment
Als u de voorzieningen nodig hebt die in een headerbestand zijn aangegeven, hoeft u geen andere headers expliciet op te nemen.
Minimality
U zou geen informatie uit een header moeten kunnen verwijderen zonder dat builds mislukken.
Opnemen wat u gebruikt (IWYU)
Van meer belang voor C ++ dan C, maar niettemin ook belangrijk in C. Als de code in een TU (noem het
code.c
) direct gebruik van de functies verklaard door een header (noem het"headerA.h"
), dancode.c
moeten#include "headerA.h"
direct, zelfs als de TU omvat een andere header (noem deze"headerB.h"
) die op dit moment"headerA.h"
.
Af en toe kunnen er voldoende redenen zijn om een of meer van deze richtlijnen te overtreden, maar u moet zich beide bewust zijn van het feit dat u de regel overtreedt en zich bewust zijn van de gevolgen hiervan voordat u deze overtreedt.
idempotentie
Als een bepaald koptekstbestand meer dan eens in een vertaaleenheid (TU) wordt opgenomen, zouden er geen compilatieproblemen mogen zijn. Dit wordt 'idempotence' genoemd; je headers moeten idempotent zijn. Bedenk hoe moeilijk het leven zou zijn als je ervoor moest zorgen dat #include <stdio.h>
maar één keer werd opgenomen.
Er zijn twee manieren om idempotentie te bereiken: headerbewakers en de #pragma once
instructie.
Header bewakers
Kopbeschermers zijn eenvoudig en betrouwbaar en voldoen aan de C-norm. De eerste niet-commentaarregels in een headerbestand moeten de volgende vorm hebben:
#ifndef UNIQUE_ID_FOR_HEADER
#define UNIQUE_ID_FOR_HEADER
De laatste regel zonder commentaar moet #endif
, eventueel met een opmerking erna:
#endif /* UNIQUE_ID_FOR_HEADER */
Alle operationele code, inclusief andere #include
richtlijnen, moet tussen deze regels staan.
Elke naam moet uniek zijn. Vaak wordt een HEADER_H_INCLUDED
zoals HEADER_H_INCLUDED
gebruikt. Sommige oudere code gebruikt een symbool dat is gedefinieerd als de header guard (bijv. #ifndef BUFSIZ
in <stdio.h>
), maar het is niet zo betrouwbaar als een unieke naam.
Een optie zou zijn om een gegenereerde MD5 (of andere) hash te gebruiken voor de header guard naam. Vermijd het emuleren van de schema's die worden gebruikt door systeemkoppen die vaak namen gebruiken die zijn gereserveerd voor de implementatie - namen die beginnen met een onderstrepingsteken gevolgd door een ander onderstrepingsteken of een hoofdletter.
De #pragma once
richtlijn
Als alternatief ondersteunen sommige compilers de #pragma once
instructie die hetzelfde effect heeft als de drie regels die worden weergegeven voor header-bewakers.
#pragma once
De compilers die #pragma once
ondersteunen #pragma once
zijn MS Visual Studio en GCC en Clang. Als draagbaarheid echter een probleem is, is het beter om header-bewakers te gebruiken of beide te gebruiken. Moderne compilers (die C89 of later ondersteunen) moeten pragma's negeren die ze niet herkennen ('pragma's die niet worden herkend door de implementatie worden genegeerd'), maar oude versies van GCC waren niet zo toegeeflijk.
Self-containment
Moderne headers moeten op zichzelf staan, wat betekent dat een programma dat gebruik moet maken van de voorzieningen die zijn gedefinieerd door header.h
die header kan bevatten ( #include "header.h"
) en zich geen zorgen hoeft te maken of andere headers eerst moeten worden opgenomen.
Aanbeveling: headerbestanden moeten op zichzelf staan.
Historische regels
Historisch gezien was dit een licht omstreden onderwerp.
Er was eens een ander millennium, de AT&T Indian Hill C-stijl en coderingsstandaarden verklaarden:
Koptekstbestanden mogen niet worden genest. De proloog voor een headerbestand moet daarom beschrijven wat andere headers moeten zijn
#include
d om de header functioneel te maken. In extreme gevallen, waarbij een groot aantal header-bestanden in verschillende bronbestanden moet worden opgenomen, is het acceptabel om alle gangbare#include
s in één include-bestand te plaatsen.
Dit is de antithese van zelfbeheersing.
Moderne regels
Sindsdien is de mening echter in de tegenovergestelde richting gegaan. Als een bronbestand de faciliteiten moet gebruiken die worden aangegeven door een header header.h
, moet de programmeur kunnen schrijven:
#include "header.h"
en (alleen als de juiste zoekpaden op de opdrachtregel zijn ingesteld), worden alle benodigde vereiste headers opgenomen door header.h
zonder dat verdere headers aan het bronbestand hoeven te worden toegevoegd.
Dit biedt een betere modulariteit voor de broncode. Het beschermt de bron ook tegen het raadsel "raden waarom deze koptekst is toegevoegd" die ontstaat nadat de code een decennium of twee is gewijzigd en gehackt.
De coderingsnormen van de NASA Goddard Space Flight Center (GSFC) voor C is een van de modernere normen - maar is nu een beetje moeilijk op te sporen. Daarin staat dat headers op zichzelf moeten staan. Het biedt ook een eenvoudige manier om ervoor te zorgen dat headers op zichzelf staan: het implementatiebestand voor de header moet de header als eerste header bevatten. Als het niet op zichzelf staat, zal die code niet compileren.
De redenering van GSFC omvat:
§2.1.1 Koptekst bevat motivering
Deze standaard vereist dat de kop van een eenheid
#include
bevat instructies voor alle andere koppen die door de kop van de eenheid worden vereist. Door#include
voor de unit-header als eerste in de body te plaatsen, kan de compiler controleren of de header alle vereiste#include
instructies bevat.
Een alternatief ontwerp, niet toegestaan door deze standaard, staat geen
#include
in headers toe; alle#includes
worden gedaan in de body-bestanden. Unit-headerbestanden moeten dan #ifdef-instructies bevatten die controleren of de vereiste headers in de juiste volgorde zijn opgenomen.
Een voordeel van het alternatieve ontwerp is dat de lijst
#include
in het#include
precies de afhankelijkheidslijst is die nodig is in een makefile, en deze lijst wordt gecontroleerd door de compiler. Bij het standaardontwerp moet een tool worden gebruikt om de afhankelijkheidslijst te genereren. Alle door de branche aanbevolen ontwikkelomgevingen bieden echter een dergelijk hulpmiddel.
Een groot nadeel van het alternatieve ontwerp is dat als de vereiste koplijst van een eenheid verandert, elk bestand dat die eenheid gebruikt moet worden bewerkt om de lijst met
#include
bij te werken. Ook kan de vereiste koptekstlijst voor een compilerbibliotheekeenheid verschillen voor verschillende doelen.
Een ander nadeel van het alternatieve ontwerp is dat headerbestanden van compileerbibliotheek en andere bestanden van derden moeten worden aangepast om de vereiste
#ifdef
instructies toe te voegen.
Zelfbeheersing betekent dus dat:
- Als een header
header.h
een nieuwe geneste headerextra.h
, hoeft u niet elk bronbestand datheader.h
gebruikt teheader.h
om te zien of uextra.h
moet toevoegen. - Als een header
header.h
niet langer een specifieke headernotneeded.h
hoeft te bevatten, hoeft u niet elk bronbestand datheader.h
gebruikt teheader.h
om te zien of unotneeded.h
veilig kunt verwijderen (maar zie Opnemen wat u gebruikt . - U hoeft niet de juiste volgorde te bepalen voor het opnemen van de vereiste kopteksten (die een topologische soort vereist om het werk goed te doen).
Zelfbeheersing controleren
Zie Koppelen met een statische bibliotheek voor een script chkhdr
dat kan worden gebruikt om idempotence en zelfbeheersing van een header-bestand te testen.
Minimality
Headers zijn een cruciaal mechanisme voor consistentiecontrole, maar ze moeten zo klein mogelijk zijn. Dit betekent met name dat een koptekst geen andere kopteksten mag bevatten, alleen omdat het implementatiebestand de andere kopteksten nodig heeft. Een header mag alleen die headers bevatten die nodig zijn voor een consument van de beschreven services.
Een projectkop mag bijvoorbeeld <stdio.h>
niet bevatten, tenzij een van de functie-interfaces het type FILE *
(of een van de andere typen die alleen in <stdio.h>
gedefinieerd). Als een interface size_t
gebruikt, is de kleinste header die volstaat <stddef.h>
. Het is duidelijk dat als er een andere header wordt opgenomen die size_t
definieert, het ook niet nodig is om <stddef.h>
te nemen.
Als de headers minimaal zijn, wordt de compilatietijd ook tot een minimum beperkt.
Het is mogelijk om headers te bedenken die als enige doel hebben om veel andere headers op te nemen. Deze blijken op de lange termijn zelden een goed idee te zijn, omdat maar weinig bronbestanden alle voorzieningen nodig hebben die door alle headers worden beschreven. Er kan bijvoorbeeld een <standard-ch>
worden bedacht die alle standaard C-headers bevat - met zorg omdat sommige headers niet altijd aanwezig zijn. Zeer weinig programma's maken echter gebruik van de voorzieningen van <locale.h>
of <tgmath.h>
.
Opnemen wat u gebruikt (IWYU)
Google's include What You Use- project, of IWYU, zorgt ervoor dat bronbestanden alle headers bevatten die in de code worden gebruikt.
Stel dat een bronbestand source.c
bevat een kop arbitrary.h
die op zijn beurt toevallig omvat freeloader.h
, maar het bronbestand uitdrukkelijk en onafhankelijk maakt ook gebruik van de faciliteiten van freeloader.h
. Alles is goed om mee te beginnen. Vervolgens wordt op een dag arbitrary.h
gewijzigd zodat zijn klanten niet langer de faciliteiten van freeloader.h
. Opeens stopt source.c
met compileren - omdat het niet voldeed aan de IWYU-criteria. Omdat de code in source.c
expliciet gebruikmaakte van de faciliteiten van freeloader.h
, had het moeten opnemen wat het gebruikt - er zou ook een expliciete #include "freeloader.h"
in de bron moeten zijn. ( Idempotency zou ervoor hebben gezorgd dat er geen probleem was.)
De IWYU-filosofie maximaliseert de waarschijnlijkheid dat code blijft compileren, zelfs met redelijke wijzigingen in interfaces. Het is duidelijk dat als uw code een functie aanroept die vervolgens wordt verwijderd uit de gepubliceerde interface, geen enkele voorbereiding kan voorkomen dat wijzigingen noodzakelijk worden. Daarom worden wijzigingen in API's zoveel mogelijk vermeden en zijn er afschrijvingscycli voor meerdere releases, enz.
Dit is met name een probleem in C ++ omdat standaardkoppen elkaar mogen opnemen. Bronbestand file.cpp
kan één header header1.h
die op één platform een andere header header2.h
. file.cpp
kan mogelijk ook gebruikmaken van de faciliteiten van header2.h
. Dit zou in eerste instantie geen probleem zijn - de code zou compileren omdat header1.h
header2.h
bevat. Op een ander platform, of een upgrade van het huidige platform, zou header1.h
kunnen worden herzien, zodat het niet langer header2.h
bevat, en dan zou file.cpp
stoppen met compileren als resultaat.
IWYU zou het probleem herkennen en aanbevelen dat header2.h
rechtstreeks in file.cpp
wordt opgenomen. Dit zou ervoor zorgen dat het blijft compileren. Analoge overwegingen zijn ook van toepassing op C-code.
Notatie en Diversen
De C-standaard zegt dat er heel weinig verschil is tussen de #include <header.h>
en #include "header.h"
.
[
#include <header.h>
] zoekt in een reeks door de implementatie gedefinieerde plaatsen naar een header die uniek wordt geïdentificeerd door de opgegeven volgorde tussen de<
en>
scheidingstekens, en zorgt ervoor dat die richtlijn wordt vervangen door de volledige inhoud van de header. Hoe de plaatsen worden gespecificeerd of de geïdentificeerde koptekst wordt door de implementatie bepaald.
[
#include "header.h"
] zorgt ervoor dat die richtlijn wordt vervangen door de volledige inhoud van het bronbestand dat wordt geïdentificeerd door de opgegeven volgorde tussen de scheidingstekens"…"
. Het genoemde bronbestand wordt op een door de implementatie gedefinieerde manier gezocht. Als deze zoekopdracht niet wordt ondersteund of als de zoekopdracht mislukt, wordt de richtlijn opnieuw verwerkt alsof deze [#include <header.h>
] ...
Het formulier met dubbele aanhalingstekens kan dus op meer plaatsen lijken dan het formulier met de haakjes. De standaard geeft bijvoorbeeld aan dat de standaard headers tussen punthaken moeten worden opgenomen, hoewel de compilatie werkt als u in plaats daarvan dubbele aanhalingstekens gebruikt. Evenzo gebruiken standaarden zoals POSIX de indeling tussen haakjes - en dat zou u ook moeten doen. Reserve dubbele koppen voor koppen gedefinieerd door het project. Voor extern gedefinieerde kopteksten (inclusief kopteksten van andere projecten waar uw project op vertrouwt), is de hoekbracketnotatie het meest geschikt.
Merk op dat er een spatie moet staan tussen #include
en de header, hoewel de compilers daar geen spatie accepteren. Spaties zijn goedkoop.
Een aantal projecten maakt gebruik van een notatie zoals:
#include <openssl/ssl.h>
#include <sys/stat.h>
#include <linux/kernel.h>
U moet overwegen of u die naamruimtecontrole in uw project wilt gebruiken (het is waarschijnlijk een goed idee). Je moet uit de buurt blijven van de namen die door bestaande projecten worden gebruikt (met name sys
en linux
zouden slechte keuzes zijn).
Als u dit gebruikt, moet uw code zorgvuldig en consistent zijn in het gebruik van de notatie.
Gebruik geen #include "../include/header.h"
notatie.
Koptekstbestanden zouden zelden of nooit variabelen moeten definiëren. Hoewel u globale variabelen tot een minimum beperkt, zal u, als u een globale variabele nodig hebt, deze in een kop aangeven en definiëren in één geschikt bronbestand, en dat bronbestand zal de kop bevatten om de verklaring en definitie te controleren en alle bronbestanden die de variabele gebruiken, gebruiken de koptekst om deze aan te geven.
Corollary: u zult geen globale variabelen in een bronbestand declareren - een bronbestand zal alleen definities bevatten.
Koptekstbestanden moeten zelden static
functies declareren, met de opmerkelijke uitzondering van static inline
functies die in headers worden gedefinieerd als de functie in meer dan één bronbestand nodig is.
- Bronbestanden definiëren globale variabelen en globale functies.
- Bronbestanden verklaren niet het bestaan van globale variabelen of functies; ze bevatten de koptekst die de variabele of functie declareert.
- Koptekstbestanden verklaren de globale variabele en functies (en typen en ander ondersteunend materiaal).
- Koptekstbestanden definiëren geen variabelen of functies behalve (
static
)inline
functies.
Kruisverwijzing
- Waar functies in C documenteren?
- Lijst met standaard koptekstbestanden in C en C ++
- Is
inline
zonderstatic
ofextern
ooit nuttig in C99? - Hoe gebruik ik
extern
om variabelen tussen bronbestanden te delen? - Wat zijn de voordelen van een relatief pad zoals
"../include/header.h"
voor een header? - Optimalisatie van header-opname
- Moet ik elke koptekst opnemen?