Szukaj…


Wprowadzenie

We współczesnym C pliki nagłówkowe są kluczowymi narzędziami, które muszą być odpowiednio zaprojektowane i używane. Umożliwiają one kompilatorowi sprawdzanie niezależnie skompilowanych części programu.

Nagłówki deklarują typy, funkcje, makra itp., Które są potrzebne konsumentom zestawu obiektów. Cały kod korzystający z tych funkcji zawiera nagłówek. Cały kod definiujący te udogodnienia zawiera nagłówek. Pozwala to kompilatorowi sprawdzić, czy zastosowania i definicje są zgodne.

Wprowadzenie

Podczas tworzenia i używania plików nagłówkowych w projekcie C należy przestrzegać kilku wskazówek:

  • Idemopotencja

    Jeśli plik nagłówka jest wielokrotnie dołączany do jednostki tłumaczeniowej (TU), nie powinien przerywać kompilacji.

  • Samowystarczalne

    Jeśli potrzebujesz funkcji zadeklarowanych w pliku nagłówkowym, nie musisz jawnie dołączać żadnych innych nagłówków.

  • Minimalizm

    Nie powinno być możliwe usunięcie żadnych informacji z nagłówka bez spowodowania niepowodzenia kompilacji.

  • Uwzględnij to, czego używasz (IWYU)

    Bardziej dotyczy C ++ niż C, ale mimo to jest ważny również w C. Jeśli kod w JT (nazywaj go code.c . code.c ) bezpośrednio wykorzystuje funkcje zadeklarowane przez nagłówek (nazywaj go "headerA.h" ), wtedy code.c powinien #include "headerA.h" bezpośrednio, nawet jeśli JT zawiera inny nagłówek (nazwij go "headerB.h" ), który w tej chwili zawiera "headerA.h" .

Czasami mogą istnieć wystarczające powody, aby złamać jedną lub więcej z tych wytycznych, ale oboje powinniście być świadomi, że łamiecie zasadę i być świadomym konsekwencji, które należy zrobić, zanim ją złamiecie.

Idempotencja

Jeśli określony plik nagłówkowy jest zawarty więcej niż jeden raz w jednostce tłumaczeniowej (TU), nie powinno być żadnych problemów z kompilacją. Jest to nazywane „idempotencją”; nagłówki powinny być idempotentne. Pomyśl, jak trudne byłoby życie, gdybyś musiał upewnić się, że #include <stdio.h> został uwzględniony tylko raz.

Istnieją dwa sposoby osiągnięcia idempotencji: osłony nagłówków i dyrektywa #pragma once .

Nagłówki

Osłony hedera są proste i niezawodne oraz zgodne ze standardem C. Pierwsze wiersze bez komentarza w pliku nagłówkowym powinny mieć postać:

#ifndef UNIQUE_ID_FOR_HEADER
#define UNIQUE_ID_FOR_HEADER

Ostatnim wierszem bez komentarza powinien być #endif , opcjonalnie z komentarzem po nim:

#endif /* UNIQUE_ID_FOR_HEADER */

Cały kod operacyjny, w tym inne dyrektywy #include , powinien znajdować się między tymi wierszami.

Każda nazwa musi być unikalna. Często stosuje się schemat nazw, taki jak HEADER_H_INCLUDED . Niektóre starsze kody używają symbolu zdefiniowanego jako osłona nagłówka (np. #ifndef BUFSIZ w <stdio.h> ), ale nie są tak niezawodne jak unikalna nazwa.

Jedną z opcji byłoby użycie wygenerowanego skrótu MD5 (lub innego) dla nazwy osłony nagłówka. Należy unikać emulacji schematów używanych przez nagłówki systemowe, które często używają nazw zastrzeżonych dla implementacji - nazwy zaczynające się od znaku podkreślenia, po którym następuje albo inny znak podkreślenia, albo duża litera.

#pragma once Dyrektywa

Alternatywnie, niektóre kompilatory obsługują dyrektywę #pragma once która ma taki sam efekt jak trzy linie pokazane dla osłon nagłówka.

#pragma once

Kompilatory, które obsługują #pragma once obejmują #pragma once MS Visual Studio oraz GCC i Clang. Jeśli jednak chodzi o przenośność, lepiej użyć osłon nagłówka lub obu. Nowoczesne kompilatory (te obsługujące C89 lub nowsze) muszą ignorować bez komentarza pragmy, których nie rozpoznają („Każda taka pragma, która nie jest rozpoznawana przez implementację, jest ignorowana”), ale stare wersje GCC nie były tak pobłażliwe.

Samowystarczalne

Nowoczesne nagłówki powinny być samodzielne, co oznacza, że program, który musi korzystać z udogodnień zdefiniowanych przez header.h może zawierać ten nagłówek ( #include "header.h" ) i nie martwić się, czy inne nagłówki muszą zostać dołączone jako pierwsze.

Zalecenie: Pliki nagłówków powinny być samodzielne.


Zasady historyczne

Historycznie był to lekko kontrowersyjny temat.

Pewnego tysiąclecia styl i kodowanie Indian AT&T Indian Hill C stanowiły :

Pliki nagłówkowe nie powinny być zagnieżdżane. Prolog pliku nagłówkowego powinien zatem opisywać, jakie inne nagłówki muszą być #include d, aby nagłówek był funkcjonalny. W skrajnych przypadkach, gdy duża liczba plików nagłówkowych ma być zawarta w kilku różnych plikach źródłowych, dopuszczalne jest umieszczenie wszystkich typowych #include w jednym pliku dołączania.

To jest antyteza samowystarczalności.

Nowoczesne zasady

Jednak od tego czasu opinia zmierza w przeciwnym kierunku. Jeśli plik źródłowy musi korzystać z funkcji zadeklarowanych przez nagłówek nagłówka. header.h , programista powinien być w stanie napisać:

#include "header.h"

oraz (z zastrzeżeniem ustawienia prawidłowych ścieżek wyszukiwania w wierszu poleceń) wszelkie niezbędne wstępnie wymagane nagłówki zostaną dołączone przez header.h bez potrzeby dodawania kolejnych nagłówków do pliku źródłowego.

Zapewnia to lepszą modułowość kodu źródłowego. Chroni także źródło przed zagadką „zgadnij, dlaczego ten nagłówek został dodany”, która pojawia się po zmodyfikowaniu kodu i zhakowaniu go przez dekadę lub dwie.

Standardy kodowania NASA Goddard Space Flight Center (GSFC) dla C to jeden z najnowocześniejszych standardów - ale obecnie jest trochę trudny do wyśledzenia. Stwierdza, że nagłówki powinny być niezależne. Zapewnia również prosty sposób na zapewnienie, że nagłówki są samodzielne: plik implementacji nagłówka powinien zawierać nagłówek jako pierwszy nagłówek. Jeśli nie jest samowystarczalny, kod ten nie zostanie skompilowany.

Uzasadnienie podane przez GSFC obejmuje:

§2.1.1 Nagłówek zawiera uzasadnienie

Ten standard wymaga, aby nagłówek jednostki zawierał instrukcje #include dla wszystkich innych nagłówków wymaganych przez nagłówek jednostki. Umieszczenie #include jako nagłówka jednostki najpierw w treści jednostki pozwala kompilatorowi sprawdzić, czy nagłówek zawiera wszystkie wymagane instrukcje #include .

Alternatywny projekt, niedozwolony przez ten standard, nie pozwala na #include instrukcji w nagłówkach; wszystkie #includes są wykonywane w plikach body. Pliki nagłówków jednostek muszą wówczas zawierać instrukcje #ifdef, które sprawdzają, czy wymagane nagłówki są zawarte w odpowiedniej kolejności.

Jedną z zalet alternatywnego projektu jest to, że lista #include w pliku treści jest dokładnie listą zależności potrzebną w pliku makefile, a ta lista jest sprawdzana przez kompilator. W przypadku standardowego projektu do wygenerowania listy zależności należy użyć narzędzia. Jednak wszystkie środowiska programistyczne zalecane przez branżę zapewniają takie narzędzie.

Główną wadą alternatywnego projektu jest to, że jeśli wymagana lista jednostek nagłówka ulegnie zmianie, każdy plik, który korzysta z tej jednostki, musi zostać poddany edycji w celu zaktualizowania listy instrukcji #include . Ponadto wymagana lista nagłówków dla jednostki biblioteki kompilatora może być różna dla różnych obiektów docelowych.

Inną wadą alternatywnego projektu jest to, że pliki nagłówkowe biblioteki kompilatora i inne pliki stron trzecich muszą zostać zmodyfikowane w celu dodania wymaganych instrukcji #ifdef .

Zatem samowystarczalność oznacza, że:

  • Jeśli nagłówek header.h potrzebuje nowego zagnieżdżonego nagłówka extra.h , nie musisz sprawdzać każdego pliku źródłowego, który używa header.h aby zobaczyć, czy musisz dodać extra.h .
  • Jeśli nagłówek header.h nie musi już zawierać określonego nagłówka notneeded.h , nie musisz sprawdzać każdego pliku źródłowego, który używa header.h aby sprawdzić, czy możesz bezpiecznie usunąć notneeded.h (ale zobacz Uwzględnij to, czego używasz .
  • Nie trzeba ustalać prawidłowej kolejności dołączania wymaganych nagłówków (co wymaga sortowania topologicznego, aby poprawnie wykonać zadanie).

Sprawdzanie samowystarczalności

Zobacz Łączenie z biblioteką statyczną dla skryptu chkhdr którego można użyć do przetestowania idempotencji i samowystarczalności pliku nagłówkowego.

Minimalizm

Nagłówki są kluczowym mechanizmem sprawdzania spójności, ale powinny być jak najmniejsze. W szczególności oznacza to, że nagłówek nie powinien zawierać innych nagłówków tylko dlatego, że plik implementacji będzie potrzebował innych nagłówków. Nagłówek powinien zawierać tylko te nagłówki, które są niezbędne dla konsumenta opisanych usług.

Na przykład nagłówek projektu nie powinien zawierać <stdio.h> chyba że jeden z interfejsów funkcji używa typu FILE * (lub jednego z innych typów zdefiniowanych wyłącznie w <stdio.h> ). Jeśli interfejs używa size_t , najmniejszy nagłówek, który wystarcza, to <stddef.h> . Oczywiście, jeśli dołączony jest inny nagłówek, który definiuje size_t , nie ma potrzeby dołączania <stddef.h> .

Jeśli nagłówki są minimalne, czas kompilacji jest również minimalny.

Możliwe jest opracowanie nagłówków, których jedynym celem jest włączenie wielu innych nagłówków. Te rzadko okazują się dobrym pomysłem na dłuższą metę, ponieważ niewiele plików źródłowych faktycznie potrzebuje wszystkich udogodnień opisanych przez wszystkie nagłówki. Na przykład można zaprojektować <standard-ch> który obejmuje wszystkie standardowe nagłówki C - ostrożnie, ponieważ niektóre nagłówki nie zawsze są obecne. Jednak bardzo niewiele programów faktycznie korzysta z funkcji <locale.h> lub <tgmath.h> .

Uwzględnij to, czego używasz (IWYU)

Projekt Google Include What You Use lub IWYU zapewnia, że pliki źródłowe zawierają wszystkie nagłówki użyte w kodzie.

Załóżmy, że plik źródłowy source.c zawiera arbitrary.h nagłówek. arbitrary.h który z kolei przypadkowo obejmuje freeloader.h , ale plik źródłowy również jawnie i niezależnie korzysta z udogodnień z freeloader.h . Na początek wszystko jest dobrze. Następnie jeden dzień jest arbitrary.h jest zmieniany, więc jego klienci nie potrzebują już udogodnień freeloader.h . Nagle source.c przestaje się kompilować - ponieważ nie spełnia kryteriów IWYU. Ponieważ kod w source.c wyraźnie używał funkcji freeloader.h , powinien był uwzględnić to, czego używa - powinien również istnieć wyraźny #include "freeloader.h" w źródle. ( Idempotencja zapewniłaby, że nie będzie problemu).

Filozofia IWYU maksymalizuje prawdopodobieństwo kompilacji kodu nawet przy rozsądnych zmianach dokonanych w interfejsach. Oczywiście, jeśli kod wywołuje funkcję, która jest następnie usuwana z opublikowanego interfejsu, żadna ilość przygotowań nie może zapobiec koniecznym zmianom. Z tego powodu, gdy jest to możliwe, unika się zmian w interfejsach API i dlaczego istnieją cykle amortyzacji dla wielu wydań itp.

Jest to szczególny problem w C ++, ponieważ standardowe nagłówki mogą się nawzajem zawierać. Plik źródłowy file.cpp mogą obejmować jeden nagłówek header1.h że na jednej platformie zawiera inny nagłówek header2.h . file.cpp może również okazać się przydatne do korzystania z funkcji header2.h . Początkowo nie stanowiłoby to problemu - kod skompilowałby się, ponieważ header1.h zawiera header2.h Na innej platformie lub uaktualnieniu obecnej platformy header1.h może zostać zmieniony, aby nie zawierał już header2.h , a następnie file.cpp przestałby się kompilować.

IWYU zauważyłby problem i header2.h aby header2.h był dołączony bezpośrednio do file.cpp . Zapewniłoby to kompilację. Analogiczne uwagi dotyczą również kodu C.

Notacja i Miscellany

Standard C mówi, że istnieje bardzo niewielka różnica między #include <header.h> i #include "header.h" .

[ #include <header.h> ] przeszukuje sekwencję miejsc zdefiniowanych w implementacji w celu #include <header.h> nagłówka identyfikowanego jednoznacznie przez określoną sekwencję między ogranicznikami < i > i powoduje zastąpienie tej dyrektywy całą treścią nagłówka. Sposób określania miejsc lub identyfikowania nagłówka jest zdefiniowany w implementacji.

[ #include "header.h" ] powoduje zastąpienie tej dyrektywy całą zawartością pliku źródłowego określoną przez określoną sekwencję między ogranicznikami "…" . Nazwany plik źródłowy jest wyszukiwany w sposób zdefiniowany w implementacji. Jeśli to wyszukiwanie nie jest obsługiwane lub jeśli wyszukiwanie się nie powiedzie, dyrektywa jest przetwarzana ponownie tak, jakby brzmiała [ #include <header.h> ]…

Tak więc forma podwójnie cytowana może wyglądać w większej liczbie miejsc niż forma w nawiasach ostrych. Standard określa w przykładzie, że standardowe nagłówki powinny być zawarte w nawiasach kątowych, nawet jeśli kompilacja działa, jeśli zamiast tego użyjesz cudzysłowów. Podobnie standardy, takie jak POSIX, używają formatu w nawiasach kątowych - i ty też powinieneś. Zarezerwuj podwójnie cytowane nagłówki dla nagłówków zdefiniowanych w projekcie. W przypadku nagłówków zdefiniowanych zewnętrznie (w tym nagłówków z innych projektów, na których opiera się projekt), najbardziej odpowiednia jest notacja w nawiasach kątowych.

Zauważ, że pomiędzy #include i nagłówkiem powinna być spacja, nawet jeśli kompilatory nie przyjmują tam spacji. Miejsca są tanie.

Wiele projektów używa zapisu, takiego jak:

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

Powinieneś rozważyć, czy użyć tej kontroli przestrzeni nazw w swoim projekcie (jest to prawdopodobnie dobry pomysł). Powinieneś unikać nazw używanych w istniejących projektach (w szczególności zarówno sys , jak i linux byłyby złym wyborem).

Jeśli tego użyjesz, Twój kod powinien być ostrożny i spójny w stosowaniu zapisu.

Nie używaj notacji #include "../include/header.h" .

Pliki nagłówkowe powinny rzadko definiować zmienne. Chociaż będziesz utrzymywał zmienne globalne na minimalnym poziomie, jeśli potrzebujesz zmiennej globalnej, zadeklarujesz ją w nagłówku i zdefiniujesz w jednym odpowiednim pliku źródłowym, a ten plik źródłowy będzie zawierał nagłówek w celu sprawdzenia deklaracji i definicji , a wszystkie pliki źródłowe, które używają zmiennej, będą ją deklarować za pomocą nagłówka.

Następstwo: nie będziesz deklarować zmiennych globalnych w pliku źródłowym - plik źródłowy będzie zawierał tylko definicje.

Pliki nagłówkowe rzadko powinny deklarować funkcje static , z godnym uwagi wyjątkiem static inline funkcji static inline które zostaną zdefiniowane w nagłówkach, jeśli funkcja jest potrzebna w więcej niż jednym pliku źródłowym.

  • Pliki źródłowe definiują zmienne globalne i funkcje globalne.
  • Pliki źródłowe nie deklarują istnienia zmiennych globalnych ani funkcji; zawierają nagłówek, który deklaruje zmienną lub funkcję.
  • Pliki nagłówkowe deklarują zmienną globalną i funkcje (oraz typy i inne materiały pomocnicze).
  • Pliki nagłówkowe nie definiują zmiennych ani żadnych funkcji oprócz ( static ) funkcji inline .

Odsyłacze



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow