Konwencjonalnym sposobem osiągnięcia tego byłoby umieszczenie kodu źródłowego każdego modułu w oddzielnym katalogu. Każdy katalog może zawierać wszystkie pliki źródłowe i pliki nagłówkowe dla modułu.
Publiczny nagłówek każdego modułu można umieścić w osobnym, wspólnym katalogu nagłówków. Prawdopodobnie użyłbym dowiązania symbolicznego ze wspólnego katalogu do odpowiedniego katalogu modułów dla każdego nagłówka.
Reguły kompilacji po prostu stwierdzają, że żaden moduł nie może zawierać nagłówków z innych modułów, z wyjątkiem nagłówków we wspólnym katalogu. Daje to wynik, że żaden moduł nie może zawierać nagłówków z innego modułu - z wyjątkiem publicznego nagłówka (w ten sposób egzekwując prywatne bariery).
Zapobieganie cyklicznym zależnościom nie jest automatyczne. Problem polega na tym, że można ustalić, że istnieje cykliczna zależność, patrząc na kilka plików źródłowych naraz, a kompilator analizuje tylko jeden plik naraz.
Rozważ parę modułów, moduł A i moduł B, a także program, Program1, który korzysta z obu modułów.
base/include
ModuleA.h
ModuleB.h
base/ModuleA
ModuleA.h
ModuleA1.c
ModuleA2.c
base/ModuleB
ModuleB.h
ModuleB1.c
ModuleB2.c
base/Program1
Program1.c
Kompilując Program1.c, jest to w pełni uzasadnione gdyż obejmuje zarówno ModuleA.h i ModuleB.h jeśli to sprawia, że korzystanie z usług obu modułów. Tak więc ModuleA.h nie może narzekać, jeśli ModuleB.h znajduje się w tej samej jednostce tłumaczeniowej (TU), a moduł ModuleB.h nie może również złożyć zażalenia, jeśli ModuleA.h jest zawarty w tej samej JT.
Załóżmy, że moduł A jest uzasadniony w korzystaniu z urządzeń z Modułu B. Dlatego podczas kompilowania ModuleA1.c lub ModuleA2.c nie ma problemu z włączeniem zarówno ModuleA.h, jak i ModuleB.h.
Jednak, aby zapobiec cyklicznym zależnościom, użytkownik musi mieć możliwość zablokowania kodu w ModuleB1.c i ModuleB2.c przed użyciem ModuleA.h.
O ile widzę, jedynym sposobem na zrobienie tego jest jakaś technika, która wymaga prywatnego nagłówka dla modułu B, który mówi "ModułA jest już zawarty", mimo że nie jest, i to jest zawarte przed ModuleA.h jest zawsze włączony.
Szkielet ModuleA.h będzie standardowy format (i ModuleB.h będzie podobna):
#ifndef MODULEA_H_INCLUDED
#define MODULEA_H_INCLUDED
...contents of ModuleA.h...
#endif
Teraz, jeśli kod w ModuleB1.c zawiera:
#define MODULEA_H_INCLUDED
#include "ModuleB.h"
...if ModuleA.h is also included, it will declare nothing...
...so anything that depends on its contents will fail to compile...
To nie jest automatyczne.
Można wykonać analizę załączonych plików i wymagać, aby istniał topologiczny rodzaj zależności bez pętli. Wcześniej był program tsort
w systemach UNIX (i programie towarzyszącym, lorder
), który razem zapewniał potrzebne usługi, aby utworzyć bibliotekę statyczną (.a
) zawierającą pliki obiektów w kolejności, która nie wymagała ponownego skanowania archiwum. Program ranlib
, a ostatecznie ar
i ld
przejął obowiązki zarządzania ponownym skanowaniem pojedynczej biblioteki, w ten sposób powodując w szczególności nadmiarowość. Ale tsort
ma bardziej ogólne zastosowania; jest dostępny na niektórych systemach (na przykład MacOS X, RHEL 5 Linux).
Tak więc, korzystając ze śledzenia zależności z GCC plus tsort
, powinieneś być w stanie sprawdzić, czy istnieją cykle między modułami. Ale trzeba to potraktować ostrożnie.
Może być jakiś IDE lub inny zestaw narzędzi, który automatycznie obsłuży te rzeczy. Ale zwykle programiści mogą być wystarczająco zdyscyplinowani, aby uniknąć problemów - o ile wymagania i zależności między modułami są dokładnie udokumentowane.
+1 Świetna odpowiedź, w szczególności ostatni akapit. – mouviciel
Jonathan, jakie byłoby narzędzie wyboru do wdrożenia proponowanego rozwiązania? Make, CMake, Rake lub coś zupełnie innego? – thegreendroid
Nie mam zdecydowanych poglądów na bardziej nowoczesne systemy; Widziałem, jak wychwalał CMake, ale prawdopodobnie mógłbyś użyć dowolnego z nich. Może to zależeć częściowo od tego, które języki są ci najbardziej znane. Jeśli używasz Ruby, wtedy Rake ma sens, na przykład. Nadal używam zwykłego starego Make, ale robię to przez ... raczej długi czas ... więc czuję się w tym komfortowo. Jest to rozwiązanie o najniższym wspólnym mianowniku, z problemami z przenośnością. Czasami używam Autoconf, aby poradzić sobie z problemami przenośności, ale ma on swoją własną złożoną złożoność (i dramatycznie zwiększa rozmiary małych projektów). –