C/Preprocesor: Różnice pomiędzy wersjami

Usunięta treść Dodana treść
→‎Wstęp: Poprawiona literówka
Znaczniki: Z urządzenia mobilnego Z wersji mobilnej (przeglądarkowej)
kolorowanie składni, zbędne spacje, usunięto interwiki - są na Wikidanych, zbędny nawias na końcu
Linia 1:
==Wstęp==
'''Preprocesor''' jest to program, który analizuje plik źródłowy ( programu, biblioteki ) w poszukiwaniu wszystkich wyrażeń zaczynających się od "#". Na podstawie tych instrukcji generuje on kod w "czystym" języku C, który następnie jest kompilowany przez kompilator. Ponieważ za pomocą preprocesora można niemal "sterować" kompilatorem, daje on niezwykłe możliwości, które nie były dotąd znane w innych językach programowania.
 
W języku C wszystkie linijki zaczynające się od symbolu "#" są [[C/Preprocesor#Dyrektywy_preprocesora|instrukcjami ( dyrektywami) dla preprocesora]]. Nie są elementami języka C i nie podlegają bezpośrednio procesowi kompilacji.
 
 
Preprocesor może być :
* standardowy = element kompilatora
* niestandardowy <ref>[http://stackoverflow.com/questions/396644/replacements-for-the-c-preprocessor stackoverflow questions : replacements-for-the-c-preprocessor]</ref>:
** CPIP napisany w Pythonie
** GNU M4
** Cog
** Wave parser ( Boost )
** własny program napisany w :
*** Lua
*** [[PHP]], uruchamiamy z [[PHP/Pierwszy_skrypt#Konsola|konsoli]]
Linia 27:
Dyrektywy preprocesora są to wyrażenia, które występują zaraz za symbolem "#" i to właśnie za ich pomocą możemy używać preprocesora. Dyrektywa zaczyna się od znaku # i kończy się wraz z końcem linii. Aby przenieść dalszą część dyrektywy do następnej linii, należy zakończyć linię znakiem "\":
<source lang="c">
#define ADD(a,b) \
a+b
</source>
 
Omówimy teraz kilka ważniejszych dyrektyw.
 
===#include===
Najpopularniejsza dyrektywa, wstawiająca w swoje miejsce treść pliku podanego w nawiasach ostrych lub cudzysłowie. Składnia:
Linia 37 ⟶ 38:
Przykład 1 {{r|przypis1}}
<source lang="c">
#include <plik_nagłówkowy_do_dołączenia>
</source>
 
Przykład 2
<source lang="c">
#include "plik_nagłówkowy_do_dołączenia"
</source>
 
Linia 51 ⟶ 52:
 
<source lang="c">
#include "/usr/include/plik_nagłówkowy.h"
#include "C:\\borland\includes\plik_nagłówkowy.h"
</source>
 
Linia 59 ⟶ 60:
Opis: W miejsce linijki zostanie wczytany plik umieszczony w katalogu "katalog1", a ten katalog jest w katalogu z plikiem źródłowym. Inaczej mówiąc, jeśli plik źródłowy jest w katalogu "/home/user/dokumenty/zrodla", to plik nagłówkowy jest umieszczony w katalogu "/home/user/dokumenty/zrodla/katalog1"
<source lang="c">
#include "katalog1/plik_naglowkowy.h"
</source>
 
Linia 66 ⟶ 67:
Opis: Jeśli plik źródłowy jest umieszczony w katalogu "/home/user/dokumenty/zrodla", to plik nagłówkowy znajduje się w katalogu "/home/user/dokumenty/katalog1/katalog2/"
<source lang="c">
#include "../katalog1/katalog2/plik_naglowkowy.h"
</source>
 
Linia 72 ⟶ 73:
 
===#define===
Linia pozwalająca zdefiniować :
* stałą,
* funkcję
Linia 82 ⟶ 83:
 
<source lang="c">
#define NAZWA_STALEJ WARTOSC
</source>
 
lub
<source lang="c">
#define NAZWA_STALEJ
</source>
 
Linia 95 ⟶ 96:
 
Jeśli w miejscu wartości znajduje się wyrażenie, to należy je umieścić w '''nawiasach'''.
}</source lang="c">
 
#define A 5
#define B ((2)+(A))
</source>
 
Unikniemy w ten sposób [[C/Powszechne_praktyki#Konwencje_pisania_makr|niespodzianek]] związanych z priorytetem operatorów :
 
<source lang="c">
/*
 
Linia 120 ⟶ 122:
return 0;
 
}
}</source>
</source>
 
Po skompilowaniu i uruchomieniu programu otrzymujemy :
 
6 * 9 = 42
 
a powinno być :
6 * 9 = 54
 
Przyczyną błędu jest interpretacja wyrażenia :
 
1+5*8+1
 
Ze względu na brak nawiasów i priorytet operatorów ( wyższy <code>*</code> niż <code>+ </code>) jest to interpretowane jako :
 
1+(5*8)+1
 
a nie jak :
 
(1+5)*(8+1)
Linia 145 ⟶ 148:
Ta instrukcja odwołuje definicję wykonaną instrukcją ''#define''.
 
<source lang="c">#undef STALA</source>
 
===instrukcje warunkowe===
Preprocesor zawiera również instrukcje warunkowe, pozwalające na wybór tego co ma zostać skompilowane w zależności od tego, czy stała jest zdefiniowana lub jaką ma wartość:
 
====#if #elif #else #endif====
Te instrukcje uzależniają kompilacje od warunków. Ich działanie jest podobne do instrukcji warunkowych w samym języku C. I tak:
Linia 159 ⟶ 163:
Przykład:
<source lang="c">
#if INSTRUKCJE == 2
printf ("Podaj liczbę z przedziału 10 do 0\n"); /*1*/
#elif INSTRUKCJE == 1
printf ("Podaj liczbę: "); /*2*/
#else
printf ("Podaj parametr: "); /*3*/
#endif
scanf ("%d", &liczba); /*4*/
</source>
*wiersz nr 1 zostanie skompilowany jeżeli stała INSTRUKCJE będzie równa 2
Linia 200 ⟶ 204:
Przykład:
<source lang="c">
#ifdef BLAD
#error Poważny błąd kompilacji
#endif
</source>
Co jeżeli zdefiniujemy stałą BLAD, oczywiście przy pomocy dyrektywy #define? Spowoduje to wyświetlenie w trakcie kompilacji komunikatu podobnego do poniższego:
Linia 215 ⟶ 219:
 
<source lang="c">
#warning To jest bardzo prosty program
</source>
 
Linia 226 ⟶ 230:
 
Przykład:
<source lang="c">
printf ("Podaj wartość funkcji");
#line
printf ("W przedziale od 10 do 0\n); /* tutaj jest błąd - brak cudzysłowu zamykającego */
</source>
Jeżeli teraz nastąpi próba skompilowania tego kodu to kompilator poinformuje, że wystąpił błąd składni w linii '''1''', a nie np. '''258'''.
 
===#pragma===
 
Dyrektywa pragma (od angielskiego : pragmatic information) służy do tworzenia dodatkowych wątków z użyciem [[Programowanie_w_systemie_UNIX/CPU#OpenMP|OpenMP]]
 
===# oraz ##===
Dość ciekawe możliwości ma w makrach znak "#". Zamienia on stojący za nim identyfikator na napis.
<source lang="c">
 
#include <stdio.h>
#define wypisz(x) printf("%s=%i\n", #x, x)
int main()
{
int i=1;
char a=5;
wypisz(i);
wypisz(a);
return 0;
}
</source>
 
Program wypisze:
Linia 257 ⟶ 264:
 
Natomiast znaki "##" łączą dwie nazwy w jedną. Przykład:
<source lang="c">
 
#include <stdio.h>
#define abc(x) int x##_zmienna
#define wypisz(x) printf("%s=%i", #x, x)
int main()
{
abc(nasza) = 2; /* dzięki temu zadeklarujemy zmienną o nazwie nasza_zmienna */
wypisz(nasza_zmienna);
return 0;
}
</source>
 
Więcej o dobrych zwyczajach w tworzeniu makr można się dowiedzieć w rozdziale
Linia 272 ⟶ 280:
 
 
Natomiast znaki "# #" łączą dwie nazwy w jedną. Przykład:
<source lang="c">
#include <stdio.h>
 
Linia 287 ⟶ 296:
return 0;
}
</source>
 
// Program wypisze: // MÓJ DODATEK.
nasza_zmienna=2
 
// nasza_zmienna=2 // MÓJ DODATEK.
 
 
 
 
==Makra==
Preprocesor języka C umożliwia też tworzenie makr, czyli automatycznie wykonywanych czynności. Makra deklaruje się za pomocą dyrektywy #define:
<source lang="c">
 
#define MAKRO(arg1, arg2, ...) (wyrażenie)
/* można również napisać: do {instrukcje} while(0) */
/* lub jeśli jest tylko jedna instrukcja można napisać: instrukcja (bez średnika!) */
</source>
 
W momencie wystąpienia MAKRA w tekście, preprocesor automatycznie zamieni makro na wyrażenie lub instrukcje. Makra mogą być pewnego rodzaju alternatywami dla funkcji, ale powinno się ich używać tylko w specjalnych przypadkach. Ponieważ makro sprowadza się do prostego zastąpienia przez preprocesor wywołania makra przez jego tekst, jest bardzo podatne na trudne do zlokalizowania błędy (kompilator będzie podawał błędy w miejscach, w których nic nie widzimy - bo preprocesor wstawił tam tekst). Makra są szybsze (nie następuje wywołanie funkcji, które zawsze zajmuje trochę czasu{{r|przypis2}}), ale też mniej bezpieczne i elastyczne niż funkcje.
 
Przeanalizujmy teraz fragment kodu:
<source lang="c">
#include <stdio.h>
#define KWADRAT(x) ((x)*(x))
 
int main ()
#include <stdio.h>
{
#define KWADRAT(x) ((x)*(x))
printf ("2 do kwadratu wynosi %d\n", KWADRAT(2));
return 0;
int main ()
}
{
</source>
printf ("2 do kwadratu wynosi %d\n", KWADRAT(2));
return 0;
}
 
Preprocesor w miejsce wyrażenia <tt>KWADRAT(2)</tt> wstawił <tt>((2)*(2))</tt>. Zastanówmy się, co stałoby się, gdybyśmy napisali <tt>KWADRAT("2")</tt>. Preprocesor po prostu wstawi napis do kodu, co da wyrażenie <tt>(("2")*("2"))</tt>, które jest nieprawidłowe. Kompilator zgłosi błąd, ale programista widzi tylko w kodzie użycie makra a nie prawdziwą przyczynę błędu. Widać tu, że bezpieczniejsze jest użycie funkcji, które dają możliwość wyspecyfikowania typów argumentów.
 
Nawet jeżeli program się skompiluje to makro może dawać nieoczekiwany wynik. Jest tak w przypadku poniższego kodu:
<source lang="c">
 
int x = 1;
int y = KWADRAT(++x);
</source>
 
Dzieje się tak dlatego, że makra rozwijane są przez preprocesor i kompilator widzi kod:
<source lang="c">
int x = 1;
int y = ((++x)*(++x));
</source>
 
Również poniższe makra są błędne<ref>[http://www.brainbell.com/tutors/c/Advice_and_Warnings_for_C/Macros_and_Miscellaneous_Pitfalls.html brainbell : Macros_and_Miscellaneous_Pitfalls]</ref> pomimo, że opisany problem w nich nie występuje:
int x = 1;
<source lang="c">
int y = ((++x)*(++x));
#define SUMA(a, b) a + b
 
#define ILOCZYN(a, b) a * b
Również poniższe makra są błędne<ref>[http://www.brainbell.com/tutors/c/Advice_and_Warnings_for_C/Macros_and_Miscellaneous_Pitfalls.html brainbell : Macros_and_Miscellaneous_Pitfalls]</ref> pomimo, że opisany problem w nich nie występuje:
</source>
 
#define SUMA(a, b) a + b
#define ILOCZYN(a, b) a * b
 
Dają one nieoczekiwane wyniki dla wywołań:
<source lang="c">
 
SUMA(2, 2) * 2; /* 6 zamiast 8 */
ILOCZYN(2 + 2, 2 + 2); /* 8 zamiast 16 */
</source>
 
Z tego powodu istotne jest użycie nawiasów:
<source lang="c">
#define SUMA(a, b) ((a) + (b))
#define ILOCZYN(a, b) ((a) * (b))
</source>
 
#define SUMA(a, b) ((a) + (b))
#define ILOCZYN(a, b) ((a) * (b))
=== Predefiniowane makra ===
 
Linia 359 ⟶ 373:
Spróbujmy użyć tych makr w praktyce:
<source lang="c">
#include <stdio.h>
 
#if __STDC_VERSION__ >= 199901L
#include <stdio.h>
/*Jezeli mamy do dyspozycji identyfikator __func__ wykorzystajmy go.*/
#define BUG(message) fprintf(stderr, "%s:%d: %s (w funkcji %s)\n", \
#if __STDC_VERSION__ >= 199901L
/*Jezeli mamy do dyspozycji identyfikator __func__ wykorzystajmy go.*/
#define BUG(message) fprintf(stderr, "%s:%d: %s (w funkcji %s)\n", \
__FILE__, __LINE__, message, __func__)
#else
/*Jezeli __func__ nie ma, to go nie używamy*/
#define BUG(message) fprintf(stderr, "%s:%d: %s\n", \
__FILE__, __LINE__, message)
#endif
int main(void) {
printf("Program ABC, data kompilacji: %s %s\n", __DATE__, __TIME__);
 
BUG("Przykladowy komunikat bledu");
return 0;
}
</source>
 
Linia 394 ⟶ 407:
[[../Biblioteka standardowa/]]|
}}
 
[[de:C-Programmierung: Präprozessor]]
[[en:C Programming/Preprocessor]]
[[fr:Programmation C/Préprocesseur]]
[[it:C/Compilatore e precompilatore/Direttive]]
</noinclude>
]
 
[[Kategoria:C]]