Вложенный макрос FIRST_ARG - PullRequest
1 голос
/ 01 марта 2020

Я играю с C макросами и не могу понять проблему, представленную ниже.

#define FIRST_ARG(N, ...)   N

#define FIRST_ARG_EXPANDED(N, ...)   FIRST_ARG(N, __VA_ARGS__)

#define ELEMENTS(DEF, ...)  DEF(1, 2, 3), \
                            DEF(4, 5, 6)

int main()
{
    char array1[] = { FIRST_ARG(ELEMENTS(FIRST_ARG)) };
    char array2[] = { FIRST_ARG_EXPANDED(ELEMENTS(FIRST_ARG)) };
    printf("array1 size = %zu, array2 size = %zu \n", sizeof(array1), sizeof(array2));

    return 0;
}

Оказывается, что array1 и array2 различаются. FIRST_ARG(ELEMENTS(FIRST_ARG)) расширен до 1, 4, а FIRST_ARG_EXPANDED(ELEMENTS(FIRST_ARG)) результат равен 1.

Я верю, что есть некоторые объяснения этому. Посмотрите на пошаговое расширение Eclipse: enter image description here

Может ли кто-нибудь найти объяснение, основываясь на стандарте C?

1 Ответ

2 голосов
/ 01 марта 2020

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

Per C 2018 6.10.3.1 1, аргументы макроса обрабатываются для замены макроса до того, как сам макрос replace:

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

Итак, в FIRST_ARG(ELEMENTS(FIRST_ARG)) мы сначала заменим ELEMENTS(FIRST_ARG), который выдает FIRST_ARG(1, 2, 3), FIRST_ARG(4, 5, 6). Эта замена повторно сканируется для дальнейшей замены, согласно 6.10.3.4 1:

… Затем результирующая последовательность токенов предварительной обработки затем сканируется вместе со всеми последующими токенами предварительной обработки исходного файла, чтобы заменить другие имена макросов для замены .

В результате повторного сканирования FIRST_ARG(1, 2, 3), FIRST_ARG(4, 5, 6) становится 1, 4. Таким образом, результат обработки аргумента для этого оригинального FIRST_ARG равен 1, 4. Обратите внимание, что это сам первый аргумент; он не обрабатывается повторно, поэтому запятая вызывает разделение аргументов - она ​​заменяет значение первого параметра на FIRST_ARG. Затем FIRST_ARG заменяется этим значением аргумента, 1, 4.

. В FIRST_ARG_EXPANDED(ELEMENTS(FIRST_ARG)) мы сначала заменяем аргумент, в результате чего получаем аргумент 1, 4, как указано выше, и затем имеем FIRST_ARG_EXPANDED(1, 4). Это заменяется на FIRST_ARG(1, 4). Теперь это сканируется, и токены 1, 4 становятся отдельными аргументами для FIRST_ARG, поэтому результатом является первый аргумент, 1.

Таким образом, замена аргумента макроса приводит к один аргумент. Если вы хотите, чтобы он повторно сканировался для разделения на несколько аргументов через запятую, вы должны использовать другой макрос.

...