Dlaczego kompilator nie może uniknąć podwójnego importowania pliku nagłówka?

13

Nowy w C ++! Czytałem więc: http://www.learncpp.com/cpp-tutorial/110-a-first-look-at-the-preprocessor/

Nagłówki

Ponieważ pliki nagłówkowe mogą zawierać inne pliki nagłówkowe, możliwe jest, że plik nagłówkowy zostanie dołączony wiele razy.

Tworzymy więc wytyczne preprocesora, aby tego uniknąć. Ale nie jestem pewien - dlaczego kompilator nie może po prostu ... nie importować tego samego dwa razy?

Biorąc pod uwagę, że osłony nagłówków są opcjonalne (ale najwyraźniej to dobra praktyka), prawie każę myśleć, że istnieją sytuacje, w których chcesz zaimportować coś dwukrotnie. Chociaż w ogóle nie mogę wymyślić żadnego takiego scenariusza. Jakieś pomysły?

Omega
źródło
W kompilatorze MS istnieje #pragma oncekompilator, który dołącza ten plik tylko raz.
CodesInChaos

Odpowiedzi:

27

Mogą, jak pokazują nowe języki, które to robią.

Ale decyzja projektowa została podjęta wiele lat temu (kiedy kompilator C był wieloma niezależnymi etapami) i teraz, aby zachować zgodność, preprocesor musi działać w określony sposób, aby upewnić się, że stary kod kompiluje się zgodnie z oczekiwaniami.

Ponieważ C ++ dziedziczy sposób przetwarzania plików nagłówkowych po C, zachował te same techniki. Wspieramy starą decyzję projektową. Jednak zmiana sposobu działania jest zbyt ryzykowna, ponieważ wiele kodów może potencjalnie ulec uszkodzeniu. Więc teraz musimy nauczyć nowych użytkowników języka, jak korzystać ze strażników.

Istnieje kilka sztuczek z plikami nagłówkowymi, jeśli celowo dołączasz je wiele razy (w rzeczywistości zapewnia to przydatną funkcję). Chociaż, jeśli przeprojektowaliśmy paradygmat od zera, moglibyśmy uczynić z niego domyślny sposób dołączania plików.

Martin York
źródło
7

W przeciwnym razie nie byłby tak wyrazisty, biorąc pod uwagę, że zdecydowali się zachować zgodność z C, a tym samym kontynuować preprocesor zamiast tradycyjnego systemu pakowania.

Jedna rzecz, która przychodzi mi na myśl, to to, że miałem projekt, który był API. Miałem dwa pliki nagłówkowe x86lib.hi x86lib_internal.h. Ponieważ wewnętrzny był ogromny, posegregowałem „publiczne” bity do x86lib.h, aby użytkownicy nie musieli poświęcać dodatkowego czasu na kompilację.

To wprowadziło zabawny problem z zależnościami, więc skończyłem z przepływem, który wyglądał tak w x86lib_internal

  1. Ustaw WEWNĘTRZNY preprocesor zdefiniuj
  2. Dołącz x86lib.h (który był mądry, aby działać w określony sposób, gdy zdefiniowano wewnętrzny)
  3. Zrób kilka rzeczy i przedstaw kilka rzeczy używanych w x86lib.h
  4. Ustaw PO zdefiniowaniu preprocesora
  5. Dołącz ponownie x86lib.h (tym razem zignoruje wszystko oprócz wydzielonej części PO, która zależała od elementów x86lib_internal

Nie powiedziałbym, że to najlepszy sposób, aby to zrobić, ale osiągnął to, co chciałem.

Earlz
źródło
0

Jedną trudnością związaną z automatycznym wykluczaniem duplikatów nagłówków jest to, że standard C relatywnie milczy na temat tego, co oznaczają nazwy plików. Załóżmy na przykład, że kompilowany plik główny zawiera dyrektywy #include "f1.h"i #include "f2.h", a pliki znalezione dla tych dyrektyw zawierają oba #include "f3.h". Jeśli f1.hi f2.hznajdują się w różnych katalogach, ale zostały znalezione podczas wyszukiwania ścieżek dołączania, wówczas nie byłoby jasne, czy #includedyrektywy w tych plikach miały ładować ten sam f3.hplik lub różne.

Sytuacja staje się jeszcze gorsza, jeśli dodaje się możliwości dołączania plików, w tym ścieżek względnych. W niektórych przypadkach, w których pliki nagłówkowe używają ścieżek względnych dla zagnieżdżonych dyrektyw zawierających, i gdy chce się uniknąć wprowadzania jakichkolwiek zmian w dostarczonych plikach nagłówkowych, może być konieczne zduplikowanie pliku nagłówkowego w wielu miejscach w strukturze katalogów projektu. Mimo że istnieje wiele fizycznych kopii tego pliku nagłówka, należy je traktować semantycznie, jakby były pojedynczym plikiem.

Jeśli #pragma oncedyrektywa zezwala na podążanie za identyfikatorem once, z semantyką, że kompilator powinien pominąć plik, jeśli identyfikator odpowiada jednemu z wcześniej napotkanej #pragma oncedyrektywy, to semantyka byłaby jednoznaczna; kompilator, który mógłby stwierdzić, że #includedyrektywa załaduje ten sam #pragma onceplik ze znacznikami jak wcześniejszy, może zaoszczędzić trochę czasu, pomijając plik bez ponownego otwierania go, ale takie wykrycie nie będzie semantycznie ważne, ponieważ plik zostanie pominięty, czy lub nie nazwa pliku została rozpoznana jako pasująca. Nie znam jednak żadnych kompilatorów działających w ten sposób. Posiadanie kompilatora obserwuje, czy plik pasuje do wzorca #ifndef someIdentifier / #define someIdentifier / #endif [for that ifndef] / nothing followingi traktuje taką rzecz jako równoważną powyższemu #pragma once someIdentifierifsomeIdentifier pozostaje zdefiniowany, jest zasadniczo tak dobry.

supercat
źródło