Preprocesor

Program przetwarzający kod źródłowy

Preprocesorprogram komputerowy, którego zadaniem jest przetworzenie kodu źródłowego, w sposób określony przez programistę za pomocą dyrektyw preprocesora, na kod wyjściowy – tak przetworzony kod źródłowy poddawany jest następnie analizie składniowej, kompilacji, a w końcu konsolidacji.

Preprocesor jest najczęściej zintegrowany z kompilatorem języka programowania.

Preprocesor języków C i C++

edytuj

Najbardziej znane języki, które wyposażone są w preprocesor „wbudowany w język”, to C i C++. Dyrektywy preprocesora mogą występować w ogólności w dowolnym miejscu programu, a rozróżnienie ich od tekstu kodu źródłowego w językach C i C++ dokonywane jest poprzez poprzedzenie dyrektywy znakiem hash – „#”[1][2].

Do najważniejszych dyrektyw należą:

  • #include … – dyrektywa włączająca tekst innego pliku źródłowego w miejscu jej wystąpienia w pliku podlegającym aktualnie przetwarzaniu, przy czym możliwe jest zagłębione występowanie dyrektywy include,
  • #define … – definiuje stałe i makroinstrukcje (pseudofunkcje)
  • #undef … – usuwa definicje stałej lub makra
  • #if … – dyrektywy kompilacji warunkowej
  • #elif … – działa podobnie jak else if w języku C
  • #endif … – oznacza koniec bloku kompilacji warunkowej
  • #ifdef … – znaczy to samo co #if defined(…)
  • #ifndef … – znaczy to samo co #if !defined(…)
  • i inne.

Dyrektywy preprocesora C/C++ wykorzystuje się często do zabezpieczenia plików nagłówkowych przed wielokrotnym dołączaniem do tego samego projektu. Jeżeli treść pliku nagłówkowego nazwa.h obejmie się instrukcjami:

#ifndef _NAZWA_H_ /* (1) */
#define _NAZWA_H_ /* (2) */

/* ... treść właściwa ...(to co chcesz definiować np. struktury) */

#endif /* (3) */

to przy pierwszej próbie dołączenia pliku, kompilator najpierw sprawdzi, czy zdefiniowano stałą _NAZWA_H_ (może ona mieć dowolną nazwę, ten sposób jest jednak dobrym zwyczajem promowanym przez programistów) (1) – jeżeli nie, zostanie ona zdefiniowana (2) i do programu zostanie dołączona treść między (2) i (3), oznaczający koniec części dodawanej tylko przy spełnieniu warunku (1).

Niektóre kompilatory obsługują także następującą konstrukcję:

#pragma once

Zapobiega ona ponownemu załączeniu treści całego pliku, w którym została użyta. Metoda ta jednak nie ma oparcia w oficjalnym standardzie. Podobnie, jak wszystkie użycia dyrektywy #pragma, jej ewentualna obsługa jest rozszerzeniem wprowadzonym przez dany kompilator i nie jest przenośna pomiędzy różnymi narzędziami.

Preprocesor języka Clipper

edytuj

W wersji 5.x języka Clipper został zaimplementowany preprocesor wzorowany na preprocesorze języka C[3]. Znalazły się więc w nim takie dyrektywy jak: #define, #ifdef, ifndef, #include, #undef. Ale preprocesor ten zawiera również dwie dodatkowe dyrektywy: #command i #translate. Ich składnia jest następująca: #command <szablon rozpoznawczy> ⇒ <szablon wynikowy> i identycznie dla dyrektywy translate.

Dyrektywy te umożliwiają definiowanie rozkazów i pseudorozkazów. Analizowany kod źródłowy jest sprawdzany pod kątem wystąpienia zdefiniowanych szablonów w następującej kolejności:

  1. #define
  2. #translate
  3. #command

Po znalezieniu wzorca następuje podstawienie w jego miejsce tekstu utworzonego według szablonu wynikowego i ponowne sprawdzenie, czy nie występuje kolejny szablon rozpoznawczy w utworzonym kodzie.

Preprocesor języka Pike

edytuj

Również preprocesor języka Pike wzorowany jest na preprocesorze języka C. W zasadzie składnia dyrektyw preprocesora tego języka jest identyczna jak w C. Zasadnicze różnice to: brak plików nagłówkowych dołączanych dyrektywą include (choć sama dyrektywa istnieje i służy do dołączania plików z kodem źródłowym), oraz dodatkowe dyrektywy jak np. string dołączająca plik jako wartość tekstową.

Preprocesor języka PL/1

edytuj

Najbardziej rozbudowanym zestawem instrukcji dysponuje preprocesor języka PL/1 (implementacje IBM w systemie OS)[4]. W tym przypadku znakiem wyróżniającym dyrektywę preprocesora jest znak procentu ‘%’, a semantyka poleceń preprocesora jest niemal zgodna z semantyką analogicznych instrukcji języka PL/1. Lista instrukcji tego preprocesora obejmuje większość analogicznych instrukcji samego języka, w tym takich jak:

  • %DECLARE – deklaracje zmiennych preprocesora,
  • %vv=ee – przypisania,
  • %GOTO – skoku,
  • %IF – warunkowa,
  • %DO - grupująca
  • %DO vv=ee TO … BY … - pętli,
  • %PROCEDURE - procedury
  • i inne,

oraz specyficzne dla preprocesora, jak

  • %INCLUDE – włączająca,
  • i inne.

Choć preprocesor tego języka, podobnie jak w C, był wbudowany w kompilator, dzięki pewnym sztuczkom mógł być stosowany jako preprocesor dla innych języków programowania, np. FORTRAN.

Preprocesor asemblera

edytuj

W asemblerach rolę analogiczną do preprocesora pełnią makroinstrukcje, dyrektywy i pseudoinstrukcje. Powodują one wykonanie operacji na tekście kodu źródłowego w trakcie translacji wykonywanej przez program asemblera[5].

Niektóre asemblery, np. makroasembler IBM, w ramach pakietu oprogramowania dostarczają programy ułatwiające pisanie kodów źródłowych w asemblerze. Przykładem jest program SALUT wchodzący w skład ww. pakietu, który przekształcał instrukcje strukturalne na instrukcje asemblera. Działał on więc na zasadzie preprocesora lecz nie wbudowanego w program główny, tylko jako osobny program. Przed uruchomieniem procesu asemblacji należało uruchomić program SALUT, i dopiero plik wynikowy tego programu poddać procesowi asemblowania.

W programie tym instrukcje strukturalne zapisane są w pliku źródłowym i poprzedzone znakiem dolara '$'. Program dostarcza takich konstrukcji programowania strukturalnego jak instrukcja warunkowa $IF ... $ELSE ... $ENDIF, instrukcja pętli $DO w różnych wariantach, czy instrukcja przeszukiwania $SEARCH.

Preprocesor a dyrektywy kompilatora

edytuj

Niektóre implementacje języków programowania (np. Pascal, Object Pascal), choć nie posiadają wbudowanego preprocesora, to udostępniają dyrektywy kompilatora, które są rozpatrywane w trakcie kompilacji, a nie jak w przypadku preprocesora przed jej wykonaniem. Zwykle lista takich dyrektyw jest dużo mniejsza w porównaniu z listą dyrektyw preprocesora.[6][7][8]. Wynika to z budowy języków Pascal i Object Pascal, które nie używają plików nagłówkowych, natomiast instrukcje dołączania plików bibliotecznych są częścią składni języka (słowo kluczowe: uses). Języki te obsługują również stałe w przeciwieństwie do języka C, który ich nie obsługuje (pojawiły się one dopiero w C++, w języku C zamiast nich stosuje się ciągi znaków zamieniane przez preprocesor, tzw. pseudostałe). Ponadto języki Pascal i Object Pascal nie obsługują makr preprocesora, jako że ich użyteczność jest niewielka. Zamiast nich stosowane są funkcje/procedury opatrzone klauzulą inline.

W języku C# istnieje preprocesor i jest on częścią kompilatora[9]. Jednak w odróżnieniu od preprocesora języków C i C++ jest on podobny w działaniu do dyrektyw kompilatora używanych w języku Object Pascal. Oczywiście lista dyrektyw preprocesora języka C# różni się od listy dyrektyw kompilatora języka Object Pascal.

Najważniejszą (ale nie jedyną) rolą dyrektyw kompilatorów języków Pascal i Object Pascal oraz dyrektyw preprocesora języka C# jest kompilacja warunkowa.

Wady preprocesora

edytuj

Jedną z wad preprocesora jest to, że makra i pseudostałe mogą powodować problemy w trakcie kompilacji plików źródłowych w języku C/C++, ponieważ nie są sprawdzane przez kompilator, który otrzymuje kod źródłowy już po zamianie dokonanej przez preprocesor[10][11]. Także podczas debugowania programu makra mogą sprawiać problemy - kompilator nie mając o nich wiedzy, nie generuje dla nich symboli debugowania. Z tego m.in. powodu makra nie są stosowane w nowszych językach programowania takich jak: C#, Java czy Object Pascal. Natomiast w języku C++ zalecane jest używanie szablonów oraz konstrukcji językowych wprowadzonych wraz z nowszymi standardami tego języka[12].

Zobacz też

edytuj

Przypisy

edytuj
  1. Jan Bielecki, Od C do C++, programowanie obiektowe w języku C, Wydawnictwa Naukowo-Techniczne, Warszawa 1990, ISBN 83-204-1332-X
  2. Jan Bielecki, Turbo C z grafiką na IBM PC, Wydawnictwa Naukowo-Techniczne, Warszawa 1990, Seria: Mikrokomputery, ISBN 83-204-1101-7
  3. Wojciech Rogowski, Arkadiusz Serodziński, Clipper 5.0, Warszawa: Wydawnictwo PLJ, 1991, ISBN 83-85190-20-1, OCLC 749775734.
  4. Jan Bielecki, Rozszerzony PL/I i JCL w systemie OS/RIAD, Państwowe Wydawnictwo Naukowe, Warszawa 1986, Seria: Biblioteka Informatyki, ISBN 83-01-06146-4
  5. Eugeniusz Wróbel: Asembler 8086/88. Wyd. 2. Warszawa: Wydawnictwa Naukowo-Techniczne, 1992, seria: Mikrokomputery. ISBN 83-204-1504-7.
  6. Global compiler directives | Free Pascal and Lazarus Wiki. wiki.lazarus.freepascal.org. [dostęp 2021-04-23]. (ang.).
  7. Local compiler directives | Free Pascal and Lazarus Wiki. wiki.lazarus.freepascal.org. [dostęp 2021-04-23]. (ang.).
  8. Delphi Compiler Directives (List) Index | Embarcadero Docwiki. docwiki.embarcadero.com/RADStudio/Sydney/en/Main_Page. [dostęp 2021-04-23]. (ang.).
  9. Dyrektywy preprocesora języka C# | Microsoft Docs. docs.microsoft.com. [dostęp 2021-04-23]. (ang.).
  10. C/Preprocesor (w: C). Wikibooks. [dostęp 2021-04-23]. (pol.).
  11. Teoria kompilacji: Preprocessing. cpp-polska.pl. [dostęp 2021-04-23]. (pol.).
  12. constexpr expressions instead of macros (w: C# preprocessor directives) | Microsoft Docs. docs.microsoft.com. [dostęp 2021-04-23]. (ang.).