Функция макроса для печати с UB

Я изучаю, как использовать макросы, и теперь столкнулся с некоторым (скорее всего, неопределенным) поведением. Вот пример:

#include <stdio.h>

#define FOO(a, b) { \
    printf("%s%s\n", #a #b); \
} \

int main(int argc, char * argv[]){
    { printf("%s%s\n", 1 2); } //compile error
    FOO(1, 2);                 //prints 12 with some garbage
}

Демонстрация 1

Демонстрация 2

Скорее всего, у меня UB, но я копаюсь в N1570 не дал четкого объяснения этому. Самое близкое к этому, что я нашел, было 5.1.1.2(p4):

Выполняются директивы предварительной обработки, расширяются вызовы макросов и выполняются выражения унарных операторов _Pragma. Если последовательность символов, соответствующая синтаксису универсального имени символа, создается конкатенацией токенов (6.10.3.3), поведение не определено.

Вероятно, токены "1" "2" были объединены в UB, но я не уверен.


person Some Name    schedule 10.01.2019    source источник
comment
Возможно, токены 1 и 2 были объединены в UB. Вот так. Для первого %s вы получаете автоматически объединенный "12", а для второго %s у вас ничего не осталось, поэтому для этого вы печатаете мусор. Попробуйте printf("%s%s\n", #a, #b); с , между #a и #b.   -  person Blaze    schedule 10.01.2019


Ответы (2)


FOO расширяется до printf("%s%s\n", "1" "2"). Строковые литералы объединяются во время предварительной обработки, что дает printf("%s%s\n", "12").

Это неправильный вызов printf и UB. Соответствующая часть стандарта такова:

7.21.6.1 Функция fprintf
...
2 ... Если для формата недостаточно аргументов, поведение не определено.

person Lundin    schedule 10.01.2019
comment
Итак, поведение printf("%s\n", "1" "2"); четко определено? А как насчет раздела, который я цитировал? - person Some Name; 10.01.2019
comment
@SomeName Да, это так. Процитированный вами раздел совершенно неуместен и случайен, он относится к очень частному случаю, когда предварительная конкатенация токенов с ##, что совершенно отличается от конкатенации строковых литералов, создает универсальное имя символа, такое как \U1234. Универсальные имена символов — экзотическая редкая функция, которая позволяет вводить символы в исходном коде, если это не поддерживается редактором исходного кода. Это описано в 6.4.3. К твоему вопросу это не имеет никакого отношения. - person Lundin; 10.01.2019
comment
Да, это так. Вы имеете в виду, что на этапе 6. Adjacent string literal tokens are concatenated? - person Some Name; 10.01.2019
comment
@SomeName Да, токены # преобразуются в строковые литералы на этапе перевода 4, но конкатенация строковых литералов происходит на этапе перевода 6. - person Lundin; 10.01.2019

Вероятно, токены "1" и "2" были объединены в UB, но я не уверен.

Ты прав.

«1» и «2» стали «12» и перешли к первому %s в printf(). Затем второму %s нечего обрабатывать, поэтому значения мусора.

Предупреждения компилятора тоже совпадают (конечно):

prog.cc:4:12: warning: format '%s' expects a matching 'char*' argument [-Wformat=]
    4 |     printf("%s%s\n", #a #b); \
      |            ^~~~~~~~
prog.cc:9:5: note: in expansion of macro 'FOO'
    9 |     FOO(1, 2);                 //prints 12 with some garbage
      |     ^~~
prog.cc:4:16: note: format string is defined here
    4 |     printf("%s%s\n", #a #b); \
      |               ~^
      |                |
      |                char*

В своем макросе измените это:

printf("%s%s\n", #a #b);

к этому:

printf("%s%s\n", #a, #b);

где запятая поможет, как прокомментировал @Blaze. Демо

Примечание. Чтобы жестко закодированный вызов printf() работал, вам нужно сделать 1 и 2 строки; использования запятой будет недостаточно. Пример: printf("%s%s\n", "1", "2");.

person gsamaras    schedule 10.01.2019