Как дважды объединить с препроцессором C и развернуть макрос как в «arg ## _ ## MACRO»?

Предполагая малое кодирование endian.

Чтобы преобразовать в UInt16 из [UInt8], вы можете сделать что-то вроде

var x: [UInt8] = [0x01, 0x02]
var y: UInt16 = 0
y += UInt16(x[1]) << 0o10
y += UInt16(x[0]) << 0o00

. Для преобразования в UInt32 этот шаблон распространяется на

var x: [UInt8] = [0x01, 0x02, 0x03, 0x04]
var y: UInt32 = 0
y += UInt32(x[3]) << 0o30
y += UInt32(x[2]) << 0o20
y += UInt32(x[1]) << 0o10
y += UInt32(x[0]) << 0o00

Восьмеричное представление величины сдвига дает хорошую индикацию о том, сколько полных байтов смещено (8 становится 0o10, 16 становится 0o20 и т. д.).

Это можно свести к следующему для UInt16:

var x: [UInt8] = [0x01, 0x02]
let y: UInt16 = reverse(x).reduce(UInt16(0)) {
    $0 << 0o10 + UInt16($1)
}

и для UInt32:

var x: [UInt8] = [0x01, 0x02, 0x03, 0x04]
let y: UInt32 = reverse(x).reduce(UInt32(0)) {
    $0 << 0o10 + UInt32($1)
}

Сокращенная версия также работает для UInt64, а также обрабатывает значения, в которых байтовая кодировка не использует все байты, такие как [0x01, 0x02, 0x03]

138
задан Ciro Santilli 新疆改造中心996ICU六四事件 21 June 2015 в 09:35
поделиться

2 ответа

#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Честно говоря, вы не хотите знать, почему это работает. Если вы знаете, почему это работает, вы станете тем парнем на работе, который разбирается в таких вещах, и все будут приходить и задавать вам вопросы. =)

Редактировать: если вы действительно хотите знать, почему это работает, я с радостью опубликую объяснение, предполагая, что меня никто не опередит.

32
ответ дан 23 November 2019 в 23:15
поделиться

Стандартный препроцессор C

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

Два уровня косвенного обращения

В комментарии к другому ответу Кейд Ру спросил , почему для этого нужны два уровня косвенного обращения. Легкомысленный ответ заключается в том, что стандарт требует, чтобы это работало; вы, как правило, обнаруживаете, что вам также нужен эквивалентный трюк со строковым оператором.

Раздел 6.10.3 стандарта C99 охватывает «замену макросов», а 6.10.3.1 - «подстановку аргументов».

были идентифицированы вызовы макроса, подобного функции, имеет место подстановка аргументов. Параметр в списке замены, если не указано иное. с помощью токена предварительной обработки # или ## или с последующим маркером предварительной обработки ## (см. ниже), является заменяется соответствующим аргументом после того, как все содержащиеся в нем макросы были расширен. Перед заменой токены предварительной обработки каждого аргумента полностью заменены макросы, как если бы они образовали остальную часть препроцессорного файла; нет другого доступны токены предварительной обработки.

В вызове NAME (my) аргумент - «мой»; он полностью расширен до «моего»; затем он подставляется в заменяющую строку:

EVALUATOR(mine, VARIABLE)

Теперь макрос EVALUATOR обнаружен, и аргументы изолированы как «мои» и «ПЕРЕМЕННАЯ»; последний затем полностью расширяется до '3' и подставляется в заменяющую строку:

PASTER(mine, 3)

Операция этого регулируется другими правилами (6.10.3.3 'Оператор ##'):

Если в замене список макроса, подобного функции, перед параметром сразу ставится или за которым следует токен предварительной обработки ## , параметр заменяется соответствующим последовательность токенов предварительной обработки аргумента; [...]

Для вызовов макросов как объектных, так и функциональных, до того, как список замены будет пересмотрены на предмет замены других имен макросов, каждый экземпляр токена предварительной обработки ## в списке замены (не из аргумента) удаляется и предыдущая предварительная обработка токен объединяется со следующим токеном предварительной обработки.

Итак, список замены содержит x , за которым следует ## , а также ## , за которым следует y ; Итак, у нас есть:

mine ## _ ## 3

и удаление токенов ## и конкатенация токенов с обеих сторон объединяет «мой» с «_» и «3», чтобы получить:

mine_3

Это желаемый результат.


Если мы посмотрим на исходный вопрос, код был (адаптирован для использования «мой» вместо «some_function»):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

Аргумент NAME явно «мой», и он полностью раскрыт.
Следуя правилам 6.10.3.3, мы находим:

mine ## _ ## VARIABLE

который, когда операторы ## удаляются, отображается в:

mine_VARIABLE

точно так, как указано в вопросе.


Традиционный препроцессор C

Роберт Рюгер спрашивает :

Есть ли способ сделать это с помощью традиционного препроцессора C, который не имеет оператора вставки токена ## ?

Может быть, и может и нет - это зависит от препроцессора. Одним из преимуществ стандартного препроцессора является то, что он имеет надежную функцию, в то время как существовали различные реализации для предварительных стандартных препроцессоров. Одно из требований заключается в том, что когда препроцессор заменяет комментарий, он не генерирует пробел, как это требуется препроцессору ANSI. Препроцессор GCC (6.3.0) C удовлетворяет этому требованию; препроцессор Clang из XCode 8.2.1 этого не делает.

Когда это работает, это выполняет свою работу ( x-paste.c ):

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

Обратите внимание, что между fun, и VARIABLE нет пробела - это важно, потому что, если оно присутствует, оно копируется в вывод, и в итоге вы получаете mine_ 3 в качестве имени, что, конечно, синтаксически неверно. (А теперь, пожалуйста, можно мне вернуть волосы назад?)

С GCC 6.3.0 (запущенным cpp -traditional x-paste.c ) я получаю:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

С Clang из XCode 8.2. 1, я получаю:

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

Эти пробелы все портят. Замечу, что оба препроцессора правильные; разные предварительные стандартные препроцессоры демонстрировали оба поведения, что делало вставку токена чрезвычайно раздражающим и ненадежным процессом при попытке переноса кода. Стандарт с обозначением ## радикально упрощает это.

Могут быть другие способы сделать это. Однако это не работает:

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

GCC генерирует:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Close, но без кубиков. YMMV, конечно, в зависимости от используемого вами препроцессора предварительного стандарта. Откровенно говоря, если вы застряли с препроцессором, который не взаимодействует, вероятно, было бы проще организовать использование стандартного препроцессора C вместо предстандартного (обычно есть способ настроить компилятор соответствующим образом), чем использовать тратить много времени, пытаясь найти способ выполнить эту работу.

207
ответ дан 23 November 2019 в 23:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: