Реализация конкатенации строковых литералов в C и C++

Насколько я знаю, этот вопрос в равной степени относится к C и C++.

Шаг 6 «этапов перевода», указанных в стандарте C (5.1.1.2 в проекте стандарта C99), гласит, что смежные строковые литералы должны быть объединены в один литерал. Т.е.

printf("helloworld.c" ": %d: Hello "
       "world\n", 10);

Эквивалентно (синтаксически) следующему:

printf("helloworld.c: %d: Hello world\n", 10);

Однако стандарт, по-видимому, не указывает, какая часть компилятора должна обрабатывать это - препроцессор (cpp) или сам компилятор. Некоторые онлайн-исследования говорят мне, что эта функция, как правило, должна выполняться препроцессором (источник №1, источник №2 и многое другое), что имеет смысл.

Однако запуск cpp в Linux показывает, что cpp этого не делает:

eliben@eliben-desktop:~/test$ cat cpptest.c 
int a = 5;

"string 1" "string 2"
"string 3"

eliben@eliben-desktop:~/test$ cpp cpptest.c 
# 1 "cpptest.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "cpptest.c"
int a = 5;

"string 1" "string 2"
"string 3"

Итак, мой вопрос: где должна обрабатываться эта особенность языка, в препроцессоре или в самом компиляторе?

Возможно, нет единственного хорошего ответа. Будут оценены эвристические ответы, основанные на опыте, известных компиляторах и общей хорошей инженерной практике.


P.S. Если вам интересно, почему меня это волнует... Я пытаюсь выяснить, действительно ли мой Python Парсер на основе C должен обрабатывать конкатенацию строковых литералов (чего он не делает в данный момент) или оставить его для cpp, который, как он предполагает, выполняется перед ним.


person Eli Bendersky    schedule 29.06.2010    source источник
comment
Я полагаю, что это не может быть обработано препроцессором, потому что вы можете использовать макросы для создания строковых литералов, которые могут быть соседними с другими. Поскольку препроцессор не является многопроходным, у него нет возможности решить этот случай.   -  person Evan Teran    schedule 29.06.2010
comment
@Evan: Разве это не может быть обработано проходом конкатенации строк, происходящим после прохода расширения макроса? Если подумать, если преобразование строк является последним действием cpp, оно неотличимо от того, что находится в компиляторе :-)   -  person Eli Bendersky    schedule 01.07.2010


Ответы (5)


Стандарт не определяет препроцессор и компилятор, он просто указывает уже отмеченные вами этапы трансляции. Традиционно фазы с 1 по 4 выполнялись в препроцессоре, фазы с 5 по 7 — в компиляторе, а фаза 8 — в компоновщике, но ничего из этого не требуется по стандарту.

person Jerry Coffin    schedule 29.06.2010
comment
означает ли это, что cpp gcc не соответствует этой традиции обработки 1-6 в cpp? - person Eli Bendersky; 29.06.2010
comment
@Eli: см. отредактированный/правильный ответ. Я думаю, что это довольно близко к (настоящей) традиции. - person Jerry Coffin; 29.06.2010

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

Редактировать:

Ваша ссылка "т.е." в начале сообщения отвечает на вопрос:

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

person Cogwheel    schedule 29.06.2010
comment
Однако в любом случае это происходит до преобразования токенов препроцессора в настоящие токены. Например, следующее дает 5 вместо ошибки синтаксического анализа: sizeof "12" "34" - person Johannes Schaub - litb; 29.06.2010
comment
@Johannes: А как насчет того, что sizeof "12" "23" имеет какое-либо отношение к токенам препроцессора? - person David Thornley; 29.06.2010
comment
@David: ключевое слово sizeof принимает только один аргумент. Дайте ему две переменные, и он будет жаловаться. - person Karmastan; 29.06.2010
comment
@Karmastan: строки объединяются на этапе 6 перевода, а sizeof оценивается на этапе 7. На этапе 4 обрабатываются токены препроцессора. sizeof не является токеном препроцессора на момент его оценки. - person David Thornley; 29.06.2010
comment
@David, я имею в виду, что строковые литералы объединяются до того, как токены препроцессора преобразуются в реальные потоки токенов. Потому что абстрактный синтаксис sizeof literal, а не sizeof literal literal. Это один токен, отличный от pp, когда поток токенов был преобразован и анализируется фазой 7 в C++ и C. Я не говорю, что sizeof оценивается во время предварительной обработки. - person Johannes Schaub - litb; 29.06.2010
comment
@Johannes: Мои извинения, меня смутил препроцессор в токене препроцессора. Да, конкатенация — это этап 6, а преобразование токенов предварительной обработки — в начале этапа 7. - person David Thornley; 29.06.2010

В стандарте ANSI C эта деталь описана в разделе 5.1.1.2, пункт (6):

5.1.1.2 Фазы трансляции
...

4. Выполняются директивы предварительной обработки и расширяются вызовы макросов. ...

5. Каждый член исходного набора символов и управляющая последовательность в символьных константах и ​​строковых литералах преобразуются в член исполняемого набора символов.

6. Смежные маркеры литералов строковых символов конкатенированные и смежные широкие строковые литералы объединяются.

Стандарт не определяет, что реализация должна использовать препроцессор и компилятор как таковые.

Шаг 4 явно является обязанностью препроцессора.

Шаг 5 требует, чтобы был известен «набор символов выполнения». Эта информация также требуется компилятору. Компилятор проще перенести на новую платформу, если препроцессор не содержит зависимостей от платформы, поэтому шаг 5 и, следовательно, шаг 6 реализуется в компиляторе.

person Heath Hunnicutt    schedule 29.06.2010

Я бы обработал это в части синтаксического анализатора, отвечающей за токен сканирования, то есть в компиляторе. Это кажется более логичным. Препроцессор не должен знать «структуру» языка, и фактически обычно игнорирует ее, поэтому макросы могут генерировать некомпилируемый код. Он не обрабатывает ничего, кроме того, что он имеет право обрабатывать директивами, которые специально адресованы ему (# ...), и их «последствиям» (например, #define x h, которые заставили бы препроцессор изменить много x в h)

person ShinTakezou    schedule 05.07.2010
comment
то есть открытие , заполнение , закрытие , за которым следуют пробелы, а затем открытие путем закрытия (и т. д.), не приведет к созданию двух строковых токенов, которые затем будут объединены: он будет создавать один строковый токен напрямую - person ShinTakezou; 06.07.2010

Существуют хитрые правила того, как конкатенация строковых литералов взаимодействует с управляющими последовательностями. Предположим, у вас есть

const char x1[] = "a\15" "4";
const char y1[] = "a\154";
const char x2[] = "a\r4";
const char y2[] = "al";

тогда x1 и x2 должны оказаться равными согласно strcmp, и то же самое для y1 и y2. (Это то, что Хит имеет в виду, цитируя этапы перевода — escape-преобразование происходит перед конкатенацией строковых констант.) Также существует требование, чтобы, если любая из строковых констант в группа конкатенации имеет префикс L или U, вы получаете широкую строку или строку Unicode. Сложите все вместе, и окажется, что выполнять эту работу в составе «компилятора», а не «препроцессора» гораздо удобнее.

person zwol    schedule 16.07.2010