2017-01-17 74 views
9

Próbowałem zrozumieć makra wc za pomocą operatora preprocesora konkatenacji ##, ale zdałem sobie sprawę, że mam problem z tokenami. Myślałem, że to łatwe, ale w praktyce tak nie jest.Poprawne tokeny preprocesora w makro konkatenacji

Łączenie polega na łączeniu dwóch tokenów w celu utworzenia nowego tokena. ex: złączenie ( i ) lub int i *

Próbowałem

#define foo(x,y) x ## y 
foo(x,y) 

kiedy daję mu jakieś argumenty, ja się zawsze błąd mówiąc, że pasting both argument does not give a valid preprocessor token.

Na przykład dlaczego złączenie foo(1,aa) wyników w 1aa (jaki token to jest? I dlaczego jest ważny) ale foo(int,*) Wystąpił błąd.

Czy istnieje sposób na sprawdzenie, które tokeny są poprawne lub czy jest możliwe posiadanie jakiegoś dobrego linku do zrozumienia, w jaki sposób mogę to wyjaśnić w moim umyśle. (Już googlowałem w Google i SO)

Czego mi brakuje?

Będę wdzięczny.

+3

Pytanie o linki jest nie na temat. I możesz otrzymać pomoc, jeśli rzeczywiście pokazujesz, co przekazałeś, aby spowodować ten błąd. – StoryTeller

+0

Myślę, że '1aa' nie jest prawidłowym tokenem przetwarzania wstępnego –

+0

nie należy do żadnego z 6 typów tokenów, ale gcc nic o nim nie mówi, nawet ostrzeżenie? – Sabrina

Odpowiedz

5

Preprocessor żeton konkatenacji jest do generowania nowych żetonów, ale nie jest zdolny do wklejenia arbitralne język konstruuje razem (przyznają, na przykład, gcc documentation):

Jednak dwa żetony, które nie tworzą razem prawidłowy token nie może być wklejony razem . Na przykład nie można łączyć znaku x z + w dowolnej kolejności.

Więc próbą makro sprawia, że ​​wskaźnik na typ jak

#define MAKEPTR(NAME) NAME ## * 
MAKEPTR(int) myIntPtr; 

jest nieważny, jako int* dwa tokeny, a nie jeden.

Przykład Powyższy odnośnik pokazuje jednak, generowanie nowych tokenów:

#define COMMAND(NAME) { #NAME, NAME ## _command } 

struct command commands[] = 
{ 
    COMMAND (quit), 
    COMMAND (help), 
    ... 
}; 

Wynik:

struct command commands[] = 
{ 
    { "quit", quit_command }, 
    { "help", help_command }, 
    ... 
}; 

Reklamowe quit_command nie istniały wcześniej, ale zostały wygenerowane przez symboliczną konkatenacji.

Zauważ, że makro postaci

#define MAKEPTR(TYPE) TYPE* 
MAKEPTR(int) myIntPtr; 

jest ważna i rzeczywiście generuje typ wskaźnika z TYPE, np int* z int.

+0

, więc konkatenacja wymaga dwóch tokenów, aby utworzyć nowy istniejący token, prawda? – Sabrina

+0

tokeny lub parametry makra ... –

+0

Już znam ten przykład. – Sabrina

2

Tok wstępnego przetwarzania definiowany jest gramatyką języka C, patrz rozdział 6.4 obecnego standardu:

preprocessing-token: 
        header-name 
        identifier 
        pp-number 
        character-constant 
        string-literal 
        punctuator 
        each non-white-space character that cannot be one of the above 

Znaczenie każdego z tych określeń ma podane w innym miejscu gramatyki. Większość z nich nie wymaga wyjaśnień; identifier oznacza wszystko, co jest poprawną nazwą zmiennej (lub byłoby, gdyby nie było słowem kluczowym), a pp-number zawiera stałe całkowite i zmiennoprzecinkowe.

W standardzie C wynik wklejenia dwóch tokenów przetwarzania wstępnego musi być kolejnym ważnym tokenem przetwarzania wstępnego. Historycznie niektóre preprocesory pozwoliły na inne wklejanie (co jest równoznaczne z nie wklejeniem!), Ale prowadzi to do zamieszania, gdy ludzie kompilują swój kod za pomocą innego kompilatora.

+0

co z '1aaa'? – Sabrina

+0

Używanie '## 'nie jest zalecane. – Sabrina

+0

Nie mogę się dowiedzieć, dlaczego foo (-, 1) zawodzi, ponieważ -1 byłoby liczbą całkowitą. –

2

Ponieważ wydaje się, że jest to nieporozumienie, ciąg 1aa jest prawidłowym tokenem preprocessor; jest to przypadek pp-number, której definicja (§ 6.4.8 obecnego standardu C)

 pp-number: 
      digit 
      . digit 
      pp-number  digit 
      pp-number  identifier-nondigit 
      pp-number  e sign 
      pp-number  E sign 
      pp-number  p sign 
      pp-number  P sign 
      pp-number  . 

Innymi słowy, pp-number zaczyna się od cyfr lub ., po którym następuje cyfra, a następnie może zawierać dowolną sekwencję cyfr, "identyfikatory-nie-znaczniki" (to znaczy litery, podkreślenia i inne elementy, które mogą być częścią identyfikatora) lub litery e lub p (wielkie lub małe litery), po których następuje znak plus lub minus.

Oznacza to, że na przykład 0x1e+2 jest prawidłowym pp-number, natomiast 0x1f+1 nie jest (to trzy tokeny). W ważnym programie każdy kod pp-number, który przetrwał fazę przetwarzania wstępnego, musi spełniać składnię pewnej stałej liczbowej, co oznacza, że ​​program zawierający tekst 0x1e+2 zostanie uznany za nieprawidłowy. Morał, jeśli taki istnieje, to wielkoduszne użycie białych znaków; to nie kosztuje.

Intencją pp-number jest uwzględnienie wszystkiego, co może ostatecznie być liczbą w jakiejś przyszłej wersji C. (Pamiętaj, że po numerach mogą występować sufiksy literowe wskazujące typy i sygnatury, takie jak 27LU).

Jednak int* nie jest prawidłowym tokenem preprocesora. Są to dwa tokeny (podobnie jak -3), więc nie można ich utworzyć za pomocą operatora łączenia znaczników.

Kolejną dziwną konsekwencją reguły polegającej na wstawianiu tokena jest to, że nie można wygenerować prawidłowego tokena ... poprzez łączenie tokenów, ponieważ .. nie jest prawidłowym tokenem. (a##b##c musi być oszacowany w pewnej kolejności, więc nawet jeśli wszystkie trzy makra preprocesora rozszerzają się do ., musi wystąpić próba utworzenia tokena .., który nie powiedzie się w kompilatorach must, chociaż wierzę, że Visual Studio je akceptuje.)

Wreszcie, symbole komentarza /* i //nie są to tokenów; komentarze są zastępowane białymi znakami przed oddzieleniem tekstu programu na tokeny. Nie można więc utworzyć komentarza z wklejonymi tokenami (przynajmniej nie w zgodnym kompilatorze).