Historycznie modularność pojawiła się jako sposób dzielenia dużych projektów na niezależne części logiczne, aby zwiększyć czytelność, ponowne wykorzystanie kodu i podział odpowiedzialności między programistami. W C modularność realizuje się na poziomie plików — źródłowych (.c) i nagłówkowych (.h).
Problem, z którym borykają się programiści: jak zorganizować interakcję między częściami kodu, unikać powielania definicji, nie naruszyć enkapsulacji i uprościć kompilację.
Rozwiązanie — użycie separacji interfejsu i implementacji:
Przykład struktury kodu modularnego:
// mymath.h #ifndef MYMATH_H #define MYMATH_H int add(int, int); #endif // mymath.c #include "mymath.h" int add(int a, int b) { return a + b; } // main.c #include "mymath.h" #include <stdio.h> int main() { printf("%d ", add(3, 4)); return 0; }
Kluczowe cechy:
Czy można zdefiniować zmienną z extern w pliku nagłówkowym i bezpiecznie używać jej w kilku modułach?
Nie! Zmienne globalne należy definiować tylko w jednym pliku .c, a w nagłówkach — tylko deklarować za pomocą extern. W przeciwnym razie wystąpią błędy linkowania z powodu „multiple definition”.
Czy konieczne jest dołączanie każdego pliku nagłówkowego przez #include tylko raz?
Konieczne jest otaczanie każdego pliku .h strażnikami dołączania (#ifndef/#define/#endif), w przeciwnym razie przy wielokrotnym dołączeniu wystąpi konflikt deklaracji i błędy kompilacji.
Czy można zrealizować czystą enkapsulację prywatnych danych struktury (opaque pointer) w C?
Tak. Tzw. „opaque pointer” pozwala ukryć szczegóły struktury przed użytkownikiem:
// mystruct.h typedef struct MyStruct MyStruct; MyStruct* create(void); void destroy(MyStruct*); // mystruct.c struct MyStruct { int a; };
Cała logika zrealizowana w jednym długim pliku .c, powtarzający się kod, globalne zmienne kolidują, występują błędy linkowania.
Plusy:
Minusy:
Kod zdekomponowany na moduły, używane są include guards, prywatne dane są zamykane przez opaque pointer.
Plusy:
Minusy: