Jak dokładnie działa sztuczka z podwójnym naciągiem?

84

Przynajmniej niektóre preprocesory języka C pozwalają na zdefiniowanie wartości makra, a nie jego nazwy, poprzez przekazanie go przez jedno makro podobne do funkcji do drugiego, które je określa:

#define STR1(x) #x
#define STR2(x) STR1(x)
#define THE_ANSWER 42
#define THE_ANSWER_STR STR2(THE_ANSWER) /* "42" */

Przykładowe przypadki użycia tutaj .

To działa, przynajmniej w GCC i Clang (oba z -std=c99), ale nie jestem pewien, jak to działa w warunkach standardu C.

Czy takie zachowanie gwarantuje C99?
Jeśli tak, w jaki sposób C99 to gwarantuje?
Jeśli nie, w którym momencie zachowanie zmienia się ze zdefiniowanego w C na zdefiniowane w GCC?

Peter Hosey
źródło
1
Jeśli powiesz „przynajmniej niektóre”, czy to oznacza, że ​​widziałeś taki, w którym to nie działa? Jestem gotów napisać raport o błędzie do dostawcy.
Jens
@Jens: Nie; Nie mam. Każdy kompilator, którego użyłem (mianowicie GCC i Clang) implementuje to zachowanie.
Peter Hosey

Odpowiedzi:

80

Tak, to gwarantowane.

Działa, ponieważ argumenty makr są same w sobie rozwijane w makra, z wyjątkiem sytuacji , gdy nazwa argumentu makra pojawia się w treści makra z ciągiem znaków # lub paster-tokenem ##.

6.10.3.1/1:

... Po zidentyfikowaniu argumentów wywołania makra funkcyjnego następuje podstawianie argumentów. Parametr na liście zastępczej, o ile nie jest poprzedzony # lub ## tokenem przetwarzania wstępnego lub nie następuje po nim ## token przetwarzania wstępnego (patrz poniżej), jest zastępowany odpowiednim argumentem po rozwinięciu wszystkich zawartych w nim makr ...

Tak więc, jeśli to zrobisz STR1(THE_ANSWER), otrzymasz „THE_ANSWER”, ponieważ argument STR1 nie jest rozszerzany w makra. Jednak argument STR2 jest rozszerzany w makro, gdy jest podstawiany do definicji STR2, co w związku z tym daje STR1 argument o 42wartości „42”.

Steve Jessop
źródło
21

Jak zauważa Steve, jest to gwarantowane i jest gwarantowane od czasu standardu C89 - był to standard, w którym skodyfikowano operatory # i ## w makrach i nakazuje rekurencyjne rozszerzanie makr w argumentach przed zastąpieniem ich w treści wtedy i tylko wtedy, gdy treść nie stosuje znaku # ani ## do argumentu. Pod tym względem C99 pozostaje niezmienione od C89.

Chris Dodd
źródło
+1 za poświęcenie czasu na potwierdzenie, że jest również częścią C89. Niektórym z nas zależy na przenośności, w tym na tworzeniu kodu, z którego mogą korzystać ludzie kompilujący w trybie C89 - zaledwie kilka lat temu wydania gcc wciąż były domyślnie ustawione na C89 (plus rozszerzenia gcc, aby być uczciwym), ostatnio słyszałem, że MSVC nadal obsługuje tylko C89 i osadzone lub starsze systemy mają czasami tylko kompilatory C89. C89 zapewnia doskonałą przenośność „parter”, najmniej powszechny mianownik, do którego ludzie mogą dążyć, aby skompilować i uruchomić wszystko, co jest obecnie w praktycznym użyciu. Więc bardzo miło jest to zapamiętać.
mtraceur