W wielu makrach C / C ++ widzę kod makra zawinięty w coś, co wydaje się pozbawioną znaczenia do while
pętlą. Oto przykłady.
#define FOO(X) do { f(X); g(X); } while (0)
#define FOO(X) if (1) { f(X); g(X); } else
Nie widzę co do while
robi. Dlaczego po prostu tego nie napisać?
#define FOO(X) f(X); g(X)
c++
c
c-preprocessor
c++-faq
jfm3
źródło
źródło
void
typu na końcu ... jak ((void) 0) .do while
konstrukcja nie jest zgodna z instrukcjami return, więcif (1) { ... } else ((void)0)
konstrukcja ma więcej zgodnych zastosowań w standardzie C. A w GNU C wolisz konstrukcję opisaną w mojej odpowiedzi.Odpowiedzi:
Są
do ... while
iif ... else
są po to, aby średnik po makrze zawsze oznaczał to samo. Powiedzmy, że masz coś jak drugie makro.Teraz, jeśli miałbyś użyć
BAR(X);
wif ... else
wyciągu, w którym ciała wyciągu if nie były zawinięte w nawiasy klamrowe, dostałbyś złą niespodziankę.Powyższy kod rozwinąłby się w
który jest niepoprawny pod względem składniowym, ponieważ reszta nie jest już powiązana z if. Nie pomaga zawijać makr w nawiasy klamrowe, ponieważ średnik po nawiasach jest niepoprawny pod względem składniowym.
Istnieją dwa sposoby rozwiązania problemu. Pierwszym jest użycie przecinka do sekwencjonowania instrukcji w makrze bez pozbawiania go zdolności do działania jak wyrażenie.
Powyższa wersja paska
BAR
rozszerza powyższy kod w następujący sposób, który jest poprawny pod względem składniowym.To nie działa, jeśli zamiast tego
f(X)
masz bardziej skomplikowany kod, który musi przejść we własnym bloku, na przykład, aby zadeklarować zmienne lokalne. W najbardziej ogólnym przypadku rozwiązaniem jest użycie czegoś takiego,do ... while
aby makro stało się pojedynczą instrukcją, która przyjmuje średnik bez zamieszania.Nie musisz go używać
do ... while
, możesz też coś ugotowaćif ... else
, chociaż kiedyif ... else
rozszerza się wewnątrzif ... else
, prowadzi to do „ wiszącego innego ”, co może sprawić, że istniejący problem z wiszącym innym jeszcze trudniej będzie znaleźć, jak w poniższym kodzie .Chodzi o to, aby użyć średnika w kontekstach, w których wiszący średnik jest błędny. Oczywiście w tym miejscu można (i prawdopodobnie należy) argumentować, że lepiej byłoby zadeklarować
BAR
jako rzeczywistą funkcję, a nie makro.Podsumowując,
do ... while
jest tam, aby obejść niedociągnięcia preprocesora C. Kiedy te przewodniki w stylu C każą ci zrezygnować z preprocesora C, martwią się o to.źródło
#define BAR(X) (f(X), g(X))
priorytet operatora, który może zepsuć semantykę.if
instrukcje itp. W naszym kodzie używają nawiasów klamrowych, wówczas owijanie makr w ten sposób jest prostym sposobem uniknięcia problemów.if(1) {...} else void(0)
formularz jest bezpieczniejszy niżdo {...} while(0)
dla makr, których parametrami jest kod zawarty w rozszerzeniu makra, ponieważ nie zmienia zachowania słów kluczowych break ani kontynuowania. Na przykład:for (int i = 0; i < max; ++i) { MYMACRO( SomeFunc(i)==true, {break;} ) }
powoduje nieoczekiwane zachowanie, gdyMYMACRO
jest zdefiniowane jako#define MYMACRO(X, CODE) do { if (X) { cout << #X << endl; {CODE}; } } while (0)
ponieważ przerwa wpływa na pętlę while makra, a nie na pętlę for w miejscu wywołania makra.void(0)
było literówką(void)0
. I wierzę, że to nie rozwiąże „wiszących” inny problem: zawiadomienie nie ma średnik po(void)0
. W przeciwnym razie wiszące (np.if (cond) if (1) foo() else (void)0 else { /* dangling else body */ }
) Powoduje błąd kompilacji. Oto żywo przykładem wykazania goMakra to kopiowane / wklejane fragmenty tekstu, które procesor umieści w oryginalnym kodzie; autor makra ma nadzieję, że zamiana wygeneruje prawidłowy kod.
Istnieją trzy dobre „wskazówki”, aby odnieść sukces:
Pomóż makrze zachowywać się jak prawdziwy kod
Normalny kod jest zwykle zakończony średnikiem. Czy użytkownik powinien wyświetlić kod, który go nie potrzebuje ...
Oznacza to, że użytkownik oczekuje, że kompilator wygeneruje błąd, jeśli średnik jest nieobecny.
Ale naprawdę dobrym powodem jest to, że w pewnym momencie autor makra będzie musiał zastąpić makro prawdziwą funkcją (być może wbudowaną). Tak więc makro powinno naprawdę zachowywać się jak jedno.
Powinniśmy więc mieć makro wymagające średnika.
Utwórz poprawny kod
Jak pokazano w odpowiedzi jfm3, czasami makro zawiera więcej niż jedną instrukcję. A jeśli makro zostanie użyte w instrukcji if, będzie to problematyczne:
To makro można rozwinąć jako:
g
Funkcja zostanie wykonana niezależnie od wartościbIsOk
.Oznacza to, że musimy dodać zakres do makra:
Utwórz poprawny kod 2
Jeśli makro przypomina:
Możemy mieć inny problem w następującym kodzie:
Ponieważ rozwinąłby się jako:
Oczywiście ten kod się nie skompiluje. Zatem ponownie rozwiązanie wykorzystuje zakres:
Kod znów działa poprawnie.
Łącząc efekty średnika + zakresu?
Jest jeden idiom C / C ++, który wywołuje ten efekt: Pętla do / while:
Do / while może utworzyć zakres, w ten sposób enkapsulując kod makra, i na końcu potrzebuje średnika, przekształcając się w kod, który go potrzebuje.
Bonus?
Kompilator C ++ zoptymalizuje pętlę do / while, ponieważ fakt, że jego stan końcowy jest fałszywy, jest znany w czasie kompilacji. Oznacza to, że makro takie jak:
rozwinie się poprawnie jako
a następnie jest kompilowany i optymalizowany jako
źródło
void doSomething() { int i = 25 ; { int i = x + 1 ; f(i) ; } ; // was MY_MACRO(32) ; }
nie jest poprawnym rozszerzeniem;x
w ekspansji powinno być 32. Bardziej złożony problem jest to, co jest ekspansjaMY_MACRO(i+7)
. Kolejnym jest rozszerzenieMY_MACRO(0x07 << 6)
. Jest wiele dobrych rzeczy, ale jest kilka nieoznaczonych liter „i” i liter „nie”.\_\_LINE\_\_
, renderuje się jako __LINE__. IMHO, lepiej po prostu użyć formatowania kodu dla kodu; na przykład__LINE__
(co nie wymaga specjalnej obsługi). PS Nie wiem, czy to była prawda w 2012 roku; od tego czasu wprowadzili sporo ulepszeń do silnika.inline
funkcji wbudowanych (zgodnie ze standardem)@ jfm3 - Masz ładną odpowiedź na pytanie. Możesz także dodać, że makro idiom zapobiega również potencjalnie bardziej niebezpiecznym (ponieważ nie ma błędu) niezamierzonemu zachowaniu za pomocą prostych instrukcji „jeśli”:
rozwija się do:
co jest poprawne pod względem składniowym, więc nie występuje błąd kompilatora, ale ma prawdopodobnie niezamierzoną konsekwencję, że zawsze będzie wywoływana g ().
źródło
Powyższe odpowiedzi wyjaśniają znaczenie tych konstruktów, ale istnieje znacząca różnica między tymi dwoma, o których nie wspomniano. W rzeczywistości nie jest to powód do preferują
do ... while
doif ... else
konstruktu.Problemem
if ... else
konstrukcji jest to, że nie zmusza cię do wstawienia średnika. Jak w tym kodzie:Chociaż pominęliśmy średnik (przez pomyłkę), kod zostanie rozwinięty do
i po cichu się skompiluje (chociaż niektóre kompilatory mogą wyświetlać ostrzeżenie o niedostępnym kodzie). Ale
printf
oświadczenie nigdy nie zostanie wykonane.do ... while
konstrukcja nie ma takiego problemu, ponieważ jedynym prawidłowym tokenem po średnikuwhile(0)
jest średnik.źródło
FOO(1),x++;
co ponownie da nam fałszywy pozytyw. Po prostu użyjdo ... while
i to wszystko.do ... while (0)
jest to preferowane, ale ma jedną wadę: Abreak
lubcontinue
będzie kontrolowaćdo ... while (0)
pętlę, a nie pętlę zawierającą wywołanie makra. Więcif
sztuczka wciąż ma wartość.break
lub a,continue
który byłby widoczny jako wewnątrzdo {...} while(0)
pseudo-pętli makr . Nawet w parametrze makra spowodowałoby to błąd składniowy.do { ... } while(0)
zamiastif whatever
konstrukcji jest jego idiomatyczna natura.do {...} while(0)
Konstrukt jest powszechny, dobrze znany i używany przez wiele wielu programistów. Jego uzasadnienie i dokumentacja są łatwo znane. Nie w przypadkuif
konstruktu. Dlatego przeglądanie kodu zajmuje mniej wysiłku.#define CHECK(call, onerr) if (0 != (call)) { onerr } else (void)0
. Może być użyty jakCHECK(system("foo"), break;);
, gdziebreak;
ma odnosić się do pętli otaczającejCHECK()
wywołanie.Oczekuje się, że kompilatory optymalizują
do { ... } while(false);
pętle, ale istnieje inne rozwiązanie, które nie wymagałoby takiej konstrukcji. Rozwiązaniem jest użycie przecinka:lub jeszcze bardziej egzotycznie:
Chociaż będzie to działać dobrze z osobnymi instrukcjami, nie będzie działać z przypadkami, w których zmienne są konstruowane i używane jako część
#define
:W tym przypadku konieczne byłoby użycie konstrukcji do / while.
źródło
Peterson's Algorithm
.Biblioteka preprocesora Jensa Gustedta P99 (tak, fakt, że coś takiego istnieje, zaskoczyła mnie też!) Poprawia
if(1) { ... } else
konstrukcję w niewielki, ale znaczący sposób, definiując:Uzasadnieniem tego jest to, że w przeciwieństwie do
do { ... } while(0)
konstrukcjibreak
icontinue
nadal działa wewnątrz danego bloku, ale((void)0)
tworzy błąd składniowy, jeśli średnik zostanie pominięty po wywołaniu makra, co w przeciwnym razie pominie następny blok. (W rzeczywistości nie ma tutaj problemu „wiszącego innego”, ponieważelse
wiąże się z najbliższymif
, czyli tym w makrze.)Jeśli interesują Cię rzeczy, które można wykonać mniej lub bardziej bezpiecznie za pomocą preprocesora C, sprawdź tę bibliotekę.
źródło
break
(lubcontinue
) wewnątrz makra do sterowania pętlą, która zaczyna się / kończy na zewnątrz, to po prostu zły styl i ukrywa potencjalne punkty wyjścia.else ((void)0)
że ktoś może pisaćYOUR_MACRO(), f();
i będzie poprawny pod względem składniowym, ale nigdy nie zadzwonif()
. Zedo
while
jest to błąd składni.else do; while (0)
?Z niektórych powodów nie mogę skomentować pierwszej odpowiedzi ...
Niektórzy z was pokazali makra ze zmiennymi lokalnymi, ale nikt nie wspomniał, że nie można po prostu użyć żadnej nazwy w makrze! Pewnego dnia ugryzie użytkownika! Dlaczego? Ponieważ argumenty wejściowe są podstawiane w szablonie makra. A w przykładach makr używasz prawdopodobnie najczęściej używanej zmiennej nazwy i .
Na przykład, gdy następujące makro
jest używany w następującej funkcji
makro nie użyje zamierzonej zmiennej i, która jest zadeklarowana na początku some_func, ale zmienna lokalna, która jest zadeklarowana w pętli do ... while makra.
Dlatego nigdy nie używaj wspólnych nazw zmiennych w makrze!
źródło
int __i;
.mylib_internal___i
Lub podobnych.Wyjaśnienie
do {} while (0)
iif (1) {} else
upewnij się, że makro jest rozwinięte tylko do 1 instrukcji. Inaczej:rozwinąłby się w:
I
g(X)
zostanie wykonany poza instrukcjąif
sterującą. Można tego uniknąć podczas używaniado {} while (0)
iif (1) {} else
.Lepsza alternatywa
Dzięki wyrażeniu instrukcji GNU (nie będącym częścią standardowego C) masz lepszy sposób
do {} while (0)
i możesz toif (1) {} else
rozwiązać, po prostu używając({})
:Ta składnia jest zgodna z wartościami zwracanymi (zauważ, że
do {} while (0)
nie jest), jak w:źródło
Nie sądzę, żeby o tym wspomniano, więc zastanów się nad tym
zostanie przetłumaczony na
zauważ, jak
i++
makro jest oceniane dwukrotnie. Może to prowadzić do interesujących błędów.źródło
do { int macroname_i = (i); f(macroname_i); g(macroname_i); } while (/* CONSTCOND */ 0)