# и ## в макросах - PullRequest
       6

# и ## в макросах

27 голосов
/ 06 декабря 2010
  #include <stdio.h>
  #define f(a,b) a##b
  #define g(a)   #a
  #define h(a) g(a)

  int main()
  {
    printf("%s\n",h(f(1,2)));
    printf("%s\n",g(f(1,2)));
    return 0;
  }

Просто взглянув на программу, можно «ожидать», что результат будет одинаковым для обоих операторов printf. Но при запуске программы вы получите:

bash$ ./a.out
12
f(1,2)
bash$

Почему это так?

Ответы [ 3 ]

27 голосов
/ 06 декабря 2010

Потому что именно так работает препроцессор.

Одиночный '#' создаст строку из заданного аргумента, независимо от того, что содержит этот аргумент, в то время как двойной символ ## создаст новый токенобъединяя аргументы.

Попробуйте посмотреть на предварительно обработанный вывод (например, с помощью gcc -E), если хотите лучше понять, как оцениваются макросы.

19 голосов
/ 06 декабря 2010

Вхождение параметра в функциональный макрос, если только он не является операндом # или ##, раскрывается перед его заменой и повторным сканированием целого для дальнейшего расширения.Поскольку параметр g является операндом #, аргумент не раскрывается, а сразу же преобразуется в строку ("f(1,2)").Поскольку параметр h не является операндом # или ##, аргумент сначала расширяется (12), затем подставляется (g(12)), затем повторно сканируется и далее расширяетсяпроисходит ("12").

11 голосов
/ 11 сентября 2016

Ниже приведены несколько связанных с вашим понятием понятий:

Аргумент Prescan :

Макро аргументы полностью макроразвертываются перед они заменяются макросовыми телами, если только они не строковые или вставленные с другими токенами.После подстановки все тело макроса, включая замещенные аргументы, сканируется снова для макрокоманд, подлежащих расширению.В результате аргументы сканируются дважды, чтобы расширить в них вызовы макросов.

Stringification

Когда параметр макроса используется с ведущим'#', препроцессор заменяет его литеральным текстом фактического аргумента, преобразованного в строковую константу .

Вставка токена /Конкатенация токенов :

Часто полезно объединить два токена в один при расширении макросов.Это называется вставка токена или объединение токена .Оператор предварительной обработки ## выполняет вставку токена.Когда макрос раскрывается, два токена на каждой стороне каждого оператора «##» объединяются в один токен, который затем заменяет «##» и два исходных токена в раскрытии макроса.

Итак, подробный процесс вашего сценария выглядит следующим образом:

h(f(1,2))
-> h(12) // f(1,2) pre-expanded since there's no # or ## in macro h
-> g(12)  // h expanded to g
12   // g expanded

g(f(1,2))
-> "f(1,2)"  //f(1,2) is literally strigified because of the `#` in macro g. f(1,2) is NOT expanded at all.
...