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.cmoeten#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
#included 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#includes 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
#includebevat instructies voor alle andere koppen die door de kop van de eenheid worden vereist. Door#includevoor de unit-header als eerste in de body te plaatsen, kan de compiler controleren of de header alle vereiste#includeinstructies bevat.
Een alternatief ontwerp, niet toegestaan door deze standaard, staat geen
#includein headers toe; alle#includesworden 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
#includein het#includeprecies 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
#includebij 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
#ifdefinstructies toe te voegen.
Zelfbeheersing betekent dus dat:
- Als een header
header.heen nieuwe geneste headerextra.h, hoeft u niet elk bronbestand datheader.hgebruikt teheader.hom te zien of uextra.hmoet toevoegen. - Als een header
header.hniet langer een specifieke headernotneeded.hhoeft te bevatten, hoeft u niet elk bronbestand datheader.hgebruikt teheader.hom te zien of unotneeded.hveilig 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)inlinefuncties.
Kruisverwijzing
- Waar functies in C documenteren?
- Lijst met standaard koptekstbestanden in C en C ++
- Is
inlinezonderstaticofexternooit nuttig in C99? - Hoe gebruik ik
externom 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?