Załóżmy, że mamy bazę kodów, która jest używana dla wielu różnych klientów, i mamy w niej trochę kodu, który jest odpowiedni tylko dla klientów typu X. Czy lepiej jest użyć dyrektyw preprocesora, aby uwzględnić ten kod tylko u klienta typu X lub używać instrukcji if? Aby być bardziej zrozumiałym:
// some code
#if TYPE_X_COSTUMER = 1
// do some things
#endif
// rest of the code
lub
if(TYPE_X_COSTUMER) {
// do some things
}
Argumenty, o których mogę pomyśleć, to:
- Dyrektywa preprocesora skutkuje mniejszą powierzchnią kodu i mniejszą liczbą gałęzi (w przypadku nieoptymalizujących kompilatorów)
- Jeśli wyniki zawierają kod, który zawsze się kompiluje, np. Jeśli ktoś popełni błąd, który zaszkodzi nieistotnemu kodowi dla projektu, nad którym pracuje, błąd nadal będzie się pojawiał i nie uszkodzi bazy kodu. W przeciwnym razie nie będzie świadomy zepsucia.
- Zawsze mówiono mi, że wolę używać procesora niż preprocesora (jeśli jest to w ogóle argument ...)
Co jest lepsze - gdy mówimy o bazie kodu dla wielu różnych klientów?
TYPE_X_CUSTOMER
nadal jest makro preprocesora?Odpowiedzi:
Myślę, że jest jedna zaleta używania #define, o której nie wspomniałeś, a mianowicie fakt, że możesz ustawić wartość w wierszu poleceń (a więc ustawić ją ze skryptu kompilacji w jednym kroku).
Poza tym ogólnie lepiej unikać makr. Nie szanują żadnego zakresu, co może powodować problemy. Tylko bardzo głupie kompilatory nie mogą zoptymalizować warunku na podstawie stałej czasowej kompilacji. Nie wiem, czy to dotyczy twojego produktu (na przykład ważne może być utrzymanie małego kodu na platformach osadzonych).
źródło
Jeśli chodzi o wiele pytań, odpowiedź na to pytanie jest zależna . Zamiast powiedzieć, co jest lepsze, podałem raczej przykłady i cele, w których jeden jest lepszy od drugiego.
Zarówno preprocesor, jak i stały mają własne miejsca odpowiedniego użytkowania.
W przypadku procesora wstępnego kod jest usuwany przed czasem kompilacji. Dlatego najlepiej nadaje się do sytuacji, w których nie należy kompilować kodu . Może to wpłynąć na strukturę modułu, zależności i może pozwolić na wybór najlepszych segmentów kodu pod względem wydajności. W poniższych przypadkach kod należy podzielić tylko przy użyciu preprocesora.
Kod wieloplatformowy:
Na przykład, gdy kod jest kompilowany na różnych platformach, gdy kod jest zależny od określonych numerów wersji systemu operacyjnego (lub nawet wersji kompilatora - choć jest to bardzo rzadkie). Na przykład, gdy masz do czynienia z odpowiednikami kodu little-endien big-endien - muszą one być oddzielone od preprocesorów, a nie stałych. Lub jeśli kompilujesz kod dla systemu Windows i Linux, a niektóre wywołania systemowe są bardzo różne.
Łatki eksperymentalne:
Kolejnym przypadkiem, w którym powoduje to uzasadnienie, jest pewien kod eksperymentalny, który jest ryzykowny, lub niektóre główne moduły, które należy pominąć, które będą miały znaczące różnice w łączeniu lub wydajności. Powodem, dla którego chcielibyśmy wyłączyć kod za pomocą preprocesora zamiast ukrywania się pod if (), jest to, że możemy nie być pewni błędów wprowadzonych przez ten konkretny zestaw zmian i działamy w oparciu o eksperymenty. Jeśli to się nie powiedzie, nie możemy nic więcej zrobić poza wyłączeniem tego kodu w produkcji niż przepisywanie. Czasami idealnie nadaje się
#if 0
do komentowania całego kodu.Radzenie sobie z zależnościami:
Kolejny powód, dla którego możesz chcieć wygenerować Na przykład, jeśli nie chcesz obsługiwać obrazów JPEG, możesz pomóc pozbyć się kompilacji tego modułu / kodu pośredniczącego i ostatecznie biblioteka nie będzie (statycznie lub dynamicznie) linkować do tego moduł. Czasami uruchamiane są pakiety w
./configure
celu określenia dostępności takiej zależności i jeśli biblioteki nie są obecne (lub użytkownik nie chce włączyć), taka funkcja jest automatycznie wyłączana bez łączenia się z tą biblioteką. W tym przypadku zawsze korzystne jest automatyczne generowanie tych dyrektyw.Licencjonowanie:
Jednym z bardzo interesujących przykładów dyrektywy preprocesorowej jest ffmpeg . Ma kodeki, które mogą potencjalnie naruszać patenty podczas jego używania. Jeśli pobierzesz źródło i skompilujesz, aby zainstalować, zostaniesz zapytany, czy chcesz tego uniknąć. Ukrywanie kodów w niektórych przypadkach, jeśli warunki nadal mogą wylądować w sądzie!
Kod kopiuj-wklej:
makra Aka. Nie jest to wskazówka do nadmiernego używania makr - wystarczy, że makra mają znacznie bardziej zaawansowany sposób na zastosowanie odpowiednika kopiowania z przeszłości . Ale używaj go z wielką ostrożnością; i użyj go, jeśli wiesz, co robisz. Stałe oczywiście nie mogą tego zrobić. Ale można również korzystać z
inline
funkcji, jeśli jest to łatwe do wykonania.Kiedy więc używasz stałych?
Niemal wszędzie indziej.
Neater flow code:
Ogólnie rzecz biorąc, gdy używasz stałych, jest prawie nie do odróżnienia od zwykłych zmiennych, a zatem jest lepiej czytelny. Jeśli piszesz procedurę składającą się z 75 linii - mają 3 lub 4 linie po każdych 10 liniach z #ifdef BARDZO nie można odczytać . Prawdopodobnie podano podstawową stałą rządzoną przez #ifdef i używaj jej w naturalnym przepływie w każdym miejscu.
Dobrze wcięty kod: wszystkie dyrektywy preprocesora, nigdy nie działają dobrze z kodem dobrze wciętym inaczej . Nawet jeśli twój kompilator pozwala na wcięcie #def, preprocesor Pre-ANSI C nie pozwalał na spację między początkiem linii a znakiem „#”; wiodące „#” zawsze musiało być umieszczone w pierwszej kolumnie.
Konfiguracja:
Innym powodem, dla którego stałe / lub zmienne mają sens, jest to, że mogą one łatwo ewoluować od bycia połączonymi z globalsami lub w przyszłości mogą zostać rozszerzone, aby uzyskać je z plików konfiguracyjnych.
Ostatnia rzecz:
nigdy NIE UŻYWAJ dyrektyw preprocesora w
#ifdef
celu#endif
przekroczenia zakresu lub{ ... }
. tzn. początek#ifdef
lub koniec#endif
po różnych stronach{ ... }
. To jest bardzo złe; może być mylące, czasem niebezpieczne.To oczywiście nie jest wyczerpująca lista, ale pokazuje wyraźną różnicę, w przypadku której metody lepiej użyć. Tak naprawdę nie chodzi o to, co jest lepsze , ale zawsze bardziej naturalne jest użycie w danym kontekście.
źródło
Czy Twoi klienci mają dostęp do Twojego kodu? Jeśli tak, to preprocesor może być lepszą opcją. Mamy coś podobnego i używamy flag czasu kompilacji dla różnych klientów (lub specyficznych funkcji klienta). Następnie skrypt może wyodrębnić kod specyficzny dla klienta, a my wysyłamy ten kod. Klienci nie są świadomi innych klientów ani innych funkcji specyficznych dla klienta.
źródło
Kilka dodatkowych punktów wartych wspomnienia:
Kompilator może przetwarzać niektóre rodzaje wyrażeń stałych, których nie potrafi preprocesor. Na przykład preprocesor zazwyczaj nie ma możliwości oceny
sizeof()
typów innych niż pierwotne. Wif()
niektórych przypadkach może to wymusić użycie .Kompilator zignoruje większość problemów ze składnią w kodzie, który jest pomijany
#if
(niektóre problemy związane z preprocesorem mogą nadal powodować problemy), ale nalega na poprawność składniową pomijanego koduif()
. Tak więc, jeśli kod, który jest pomijany dla niektórych kompilacji, ale nie dla innych, staje się nieprawidłowy w wyniku zmian w innym miejscu pliku (np. Zmiana nazwy identyfikatora), generalnie będzie skrzeczał na wszystkich kompilacjach, jeśli jest wyłączony,if()
ale nie, jeśli jest wyłączony przez#if
. W zależności od tego, dlaczego kod jest pomijany, może to być dobre rozwiązanie.Niektóre kompilatory pominą nieosiągalny kod, podczas gdy inne nie; deklaracje o czasie przechowywania statycznego w obrębie nieosiągalnego kodu, w tym także literały łańcuchowe, mogą przydzielić miejsce, nawet jeśli żaden kod nigdy nie może wykorzystać miejsca w ten sposób przydzielonego.
Makra mogą korzystać z
if()
testów wewnątrz nich, ale nie ma niestety mechanizmu umożliwiającego preprocesorowi wykonywanie dowolnej logiki warunkowej w makrze [zwróć uwagę, że w przypadku makr? :
operator często może być lepszy niżif
, ale obowiązują te same zasady].#if
Dyrektywa może być używany do sterowania co makra uzyskać określone.Myślę, że
if()
często jest czystszy w większości przypadków, z wyjątkiem tych, które wiążą się z podejmowaniem decyzji na podstawie używanego kompilatora lub makr, które należy zdefiniować; niektóre z powyższych problemów mogą wymagać użycia#if
, nawet w przypadkach, w którychif
wydają się czystsze.źródło
Krótko mówiąc: obawy przed dyrektywami preprocesora to w zasadzie 2, wiele inkluzji i być może mniej czytelny kod i bardziej tajemnicza logika.
Jeśli twój projekt jest niewielki i nie ma prawie żadnego dużego planu na przyszłość, myślę, że obie opcje oferują taki sam stosunek zalet i wad, ale jeśli twój projekt będzie ogromny, rozważ coś innego, jak napisanie osobnego pliku nagłówka, jeśli nadal chcesz korzystać z dyrektyw preprocesora, ale pamiętaj, że takie podejście zwykle sprawia, że kod jest mniej czytelny i upewnij się, że nazwa tych stałych ma znaczenie dla logiki biznesowej samego programu.
źródło