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" ), dan code.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 header extra.h , hoeft u niet elk bronbestand dat header.h gebruikt te header.h om te zien of u extra.h moet toevoegen.
  • Als een header header.h niet langer een specifieke header notneeded.h hoeft te bevatten, hoeft u niet elk bronbestand dat header.h gebruikt te header.h om te zien of u notneeded.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



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