Почему C не позволяет объединять строки при использовании условного оператора?

95

Следующий код компилируется без проблем:

int main() {
    printf("Hi" "Bye");
}

Однако это не компилируется:

int main() {
    int test = 0;
    printf("Hi" (test ? "Bye" : "Goodbye"));
}

В чем причина этого?

Хосе Д.
источник
95
Конкатенация строк - это часть ранней фазы лексирования; это не часть выражения synatx языка C. Другими словами, не существует значения типа «строковый литерал». Скорее строковые литералы - это лексические элементы исходного кода, которые формируют значения.
Керрек С.Б.
24
Чтобы уточнить ответ @KerrekSB - объединение строк является частью предварительной обработки текста кода перед его компиляцией. Хотя тернарный оператор оценивается во время выполнения, после компиляции кода (или, если все является постоянным, это может быть выполнено во время компиляции).
Евгений Ш.
2
Подробно: в этом посте "Hi"и "Bye"являются строковыми литералами , а не строками, используемыми в стандартной библиотеке C. Со строковыми литералами компилятор будет объединять "H\0i" "B\0ye". Не то же самое сsprintf(buf,"%s%s", "H\0i" "B\0ye");
chux
15
a (some_condition ? + : - ) b
Примерно по
4
Обратите внимание, что даже printf("Hi" ("Bye"));не сработает - для этого не требуется тернарный оператор; скобок достаточно (хотя printf("Hi" test ? "Bye" : "Goodbye")и не компилируется). Существует только ограниченное количество токенов, которые могут следовать за строковым литералом. Запятая ,, открытая квадратная скобка [, закрывающая квадратная скобка ](как в 1["abc"]- и да, это ужасно), закрывающая круглая скобка ), закрывающая фигурная скобка }(в инициализаторе или подобном контексте) и точка с запятой ;допустимы (и еще один строковый литерал); Я не уверен, что есть другие.
Джонатан Леффлер

Ответы:

121

Согласно стандарту C (5.1.1.2 Фазы перевода)

1 Приоритет синтаксических правил перевода определяется следующими этапами 6)

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

И только после этого

  1. Пробелы, разделяющие токены, больше не имеют значения. Каждый токен предварительной обработки преобразуется в токен. Полученные токены анализируются синтаксически и семантически и переводятся как единица перевода .

В этой конструкции

"Hi" (test ? "Bye" : "Goodbye")

нет смежных токенов строковых литералов. Итак, эта конструкция неверна.

Влад из Москвы
источник
43
Это только повторяет утверждение, что это не разрешено в C. Это не объясняет, почему , о чем был вопрос. Не знаю, почему он набрал 26 положительных голосов за 5 часов .... и не меньше - принятие! Поздравляю.
Гонки легкости на орбите
4
Придется согласиться с @LightnessRacesinOrbit здесь. Почему не следует (test ? "Bye" : "Goodbye")переходить к одному из строковых литералов, по существу создающих "Hi" "Bye" или "Hi Goodbye"? (на мой вопрос дан ответ в других ответах)
Insane
48
@LightnessRacesinOrbit, потому что, когда люди обычно спрашивают, почему что-то не компилируется на C, они просят пояснить, какое правило нарушается, а не почему. авторы стандартов Antiquity выбрали именно такой путь.
user1717828
4
@LightnessRacesinOrbit Вопрос, который вы описываете, вероятно, не по теме. Я не вижу никаких технических причин, по которым это было бы невозможно реализовать, поэтому без окончательного ответа от авторов спецификации все ответы будут основаны на мнениях. И, как правило, он не попадает в категорию «практических» или « требующих ответа» вопросов (как указано в справочном центре ).
jpmc26
12
@LightnessRacesinOrbit Это объясняет, почему : «потому что так сказал стандарт C». Вопрос о том, почему это правило определено как определено, был бы не по теме.
user11153
135

Согласно стандарту C11, глава §5.1.1.2, объединение смежных строковых литералов:

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

происходит в фазе перевода . С другой стороны:

printf("Hi" (test ? "Bye" : "Goodbye"));

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


Чтобы подробнее рассказать о причину , на этапе предварительной обработки смежные строковые литералы объединяются и представляются как один строковый литерал (токен). Хранилище распределяется соответственно, и конкатенированный строковый литерал рассматривается как единый объект (один строковый литерал).

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

Просто FYI, для времени выполнения строки ( не литералы ) конкатенации, мы имеем библиотечную функцию , strcat()которая сцепляет две строки . Обратите внимание, в описании упоминается:

char *strcat(char * restrict s1,const char * restrict s2);

strcat()Функция добавляет копию строки , указанной s2(включая нулевой символ завершающего) до конца строки , на которую указываетs1 . Начальный символ s2заменяет нулевой символ в конце s1. [...]

Итак, мы видим, что s1это строка , а не строковый литерал . Однако, поскольку содержимое s2никоим образом не изменяется, оно вполне может быть строковым литералом .

Сурав Гош
источник
1
вы можете добавить дополнительное объяснение strcat: целевой массив должен быть достаточно длинным, чтобы принимать символы от s2плюс нулевой терминатор после символов, уже присутствующих там.
chqrlie
39

Конкатенация строковых литералов выполняется препроцессором во время компиляции. У этой конкатенации нет возможности узнать значение test, которое не известно, пока программа не выполнится. Следовательно, эти строковые литералы нельзя объединить.

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

Но даже если бы это ограничение не было сформулировано таким образом или если бы ограничение было построено по-другому, ваш пример все равно было бы невозможно реализовать без преобразования конкатенации во время выполнения. И для этого у нас есть библиотечные функции, такие как strcat.

Гонки легкости на орбите
источник
3
Я просто читаю предположения. Хотя то, что вы говорите, в значительной степени верно, вы не можете предоставить источники, поскольку их нет. Единственный источник в отношении C - это стандартный документ, который (хотя во многих случаях он кажется очевидным) не указывает, почему некоторые вещи такие, какие они есть, а просто заявляет, что они должны быть такими конкретными. Так что быть придирчивым к ответу Влада из Москвы неуместно. Поскольку ОП можно разбить на "Почему так?" -Где единственный правильный ответ источника - «Потому что это C, и именно так определяется C», это единственный буквально прямой правильный ответ.
dhein
1
Это (признается) отсутствие объяснения. Но здесь опять же, как говорится, ответ Влада служит в большей степени объяснением основной проблемы, чем ваш. Снова сказал: Хотя я могу подтвердить, что предоставленная вами информация является относящейся к делу и верной, я не согласен с вашими жалобами. и хотя я бы не стал считать вас оффтопом, с моей точки зрения, это больше оффтоп, чем на самом деле Vlads.
dhein
11
@Zaibis: Источник - это я. Ответ Влада - это вовсе не объяснение; это просто подтверждение предпосылки вопроса. Конечно, ни один из них не «не по теме» (вы можете узнать, что означает этот термин). Но вы имеете право на свое мнение.
Гонки легкости на орбите
Даже после прочтения вышеупомянутых комментариев мне все еще интересно, кто проголосовал против этого ответа ᶘ ᵒᴥᵒᶅ Я считаю, что это идеальный ответ, если OP не попросит дополнительных разъяснений по этому ответу.
Mohit Jain
2
Я не могу понять, почему этот ответ приемлем для вас, а ответ @ VladfromMoscow - нет, когда они оба говорят одно и то же, и когда его цитируют, а ваш - нет.
Маркиз Лорн,
30

Потому что у C нет stringтипа. Строковые литералы компилируются в charмассивы, на которые ссылается char*указатель.

C позволяет комбинировать смежные литералы во время компиляции , как в вашем первом примере. Сам компилятор C имеет некоторые знания о строках. Но эта информация отсутствует во время выполнения, и, следовательно, не может произойти конкатенация.

В процессе компиляции ваш первый пример «переводится» на:

int main() {
    static const char char_ptr_1[] = {'H', 'i', 'B', 'y', 'e', '\0'};
    printf(char_ptr_1);
}

Обратите внимание, как две строки объединяются компилятором в один статический массив до того, как программа будет запущена.

Однако ваш второй пример "переведен" примерно так:

int main() {
    static const char char_ptr_1[] = {'H', 'i', '\0'};
    static const char char_ptr_2[] = {'B', 'y', 'e', '\0'};
    static const char char_ptr_3[] = {'G', 'o', 'o', 'd', 'b', 'y', 'e', '\0'};
    int test = 0;
    printf(char_ptr_1 (test ? char_ptr_2 : char_ptr_3));
}

Должно быть понятно, почему это не компилируется. Тернарный оператор ?оценивается во время выполнения, а не во время компиляции, когда «строки» больше не существуют как таковые, а только как простые charмассивы, на которые ссылаются char*указатели. В отличие от смежных строковых литералов , указатели на соседние символы представляют собой просто синтаксическую ошибку.

Без подписи
источник
2
Отличный ответ, возможно, лучший здесь. «Должно быть ясно, почему это не компилируется». Вы можете рассмотреть вариант расширения этого с помощью «потому что тернарный оператор является условным, вычисляемым во время выполнения, а не во время компиляции ».
кот
Не должно static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};быть static const char *char_ptr_1 = "HiBye";и аналогично для остальных указателей?
Spikatrix
@CoolGuy Когда вы пишете, static const char *char_ptr_1 = "HiBye";компилятор переводит строку в static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};, так что нет, это не должно быть написано «как строка». Как сказано в ответе, строки компилируются в массив символов, и если вы назначаете массив символов в его самой «сырой» форме, вы бы использовали список символов, разделенных запятыми, как напримерstatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
Анкуш
3
@ Анкуш Да. Но хотя static const char str[] = {'t', 'e', 's', 't', '\0'};это то же самое static const char str[] = "test";, static const char* ptr = "test";но не то же самое, что static const char* ptr = {'t', 'e', 's', 't', '\0'};. Первый действителен и будет компилироваться, но второй недействителен и делает то, что вы ожидаете.
Spikatrix
Я уточнил последний абзац и исправил примеры кода, спасибо!
Без подписи
13

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

#include <stdio.h>
#define ccat(s, t, a, b) ((t)?(s a):(s b))

int
main ( int argc, char **argv){
  printf("%s\n", ccat("hello ", argc > 2 , "y'all", "you"));
  return 0;
}
Эрик
источник
10

В чем причина этого?

Ваш код, использующий тернарный оператор, условно выбирает между двумя строковыми литералами. Независимо от известного или неизвестного состояния, это не может быть оценено во время компиляции, поэтому оно не может компилироваться. Даже это заявление printf("Hi" (1 ? "Bye" : "Goodbye"));не компилируется. Причина подробно объяснена в ответах выше. Другая возможность сделать такой оператор, используя тернарный оператор, пригодный для компиляции , также будет включать тег формата и результат оператора тернарного оператора, отформатированный как дополнительный аргумент для printf. Даже в этом случае при printf()распечатке создается впечатление, что эти строки «сцеплены» только во время выполнения и уже в самом начале .

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}
user3078414
источник
3
SO - это не учебный сайт. Вы должны дать ответ на OP, а не учебное пособие.
Мичи
1
Это не отвечает на вопрос ОП. Это может быть попытка решить основную проблему OP, но мы действительно не знаем, что это такое.
Кейт Томпсон
1
printfне требует спецификатора формата; если бы только конкатенация была выполнена во время компиляции (а это не так), OP использование printf было бы допустимым.
Дэвид Конрад
Спасибо за ваше замечание, @ Дэвид Конрад. Моя неаккуратная формулировка действительно может выглядеть так, как будто для утверждения printf()требуется тег формата, что абсолютно неверно. Исправлено!
user3078414
Это лучшая формулировка. +1 Спасибо.
Дэвид Конрад
7

У printf("Hi" "Bye");вас есть два последовательных массива char, которые компилятор может превратить в один массив.

У printf("Hi" (test ? "Bye" : "Goodbye"));вас есть один массив, за которым следует указатель на char (массив, преобразованный в указатель на его первый элемент). Компилятор не может объединить массив и указатель.

pmg
источник
0

Чтобы ответить на вопрос - я бы перешел к определению printf. Функция printf ожидает в качестве аргумента const char * . Любой строковый литерал, такой как «Hi», является const char *; однако такое выражение, как (test)? "str1" : "str2"НЕ является const char *, потому что результат такого выражения обнаруживается только во время выполнения и, следовательно, является неопределенным во время компиляции, факт, который должным образом заставляет компилятор жаловаться. С другой стороны - это прекрасно работаетprintf("hi %s", test? "yes":"no")

Stats_Lover
источник
* однако такое выражение, как (test)? "str1" : "str2"НЕ const char*... Конечно, есть! Это не выражение постоянной, но ее тип является const char * . Было бы прекрасно написать printf(test ? "hi " "yes" : "hi " "no"). Проблема OP не имеет ничего общего printf, "Hi" (test ? "Bye" : "Goodbye")это синтаксическая ошибка независимо от контекста выражения.
chqrlie
Согласовано. Я перепутал вывод выражения с самим выражением
Stats_Lover 01
-4

Это не компилируется, потому что список параметров для функции printf

(const char *format, ...)

и

("Hi" (test ? "Bye" : "Goodbye"))

не соответствует списку параметров.

gcc пытается понять это, представив, что

(test ? "Bye" : "Goodbye")

представляет собой список параметров и жалуется, что «Hi» не является функцией.

Родботы
источник
6
Добро пожаловать в Stack Overflow. Вы правы в том, что оно не соответствует printf()списку аргументов, но это потому, что выражение недействительно нигде - не только в printf()списке аргументов. Другими словами, вы выбрали слишком узкую причину проблемы; общая проблема в том, что "Hi" (это недопустимо в C, не говоря уже о вызове printf(). Я предлагаю вам удалить этот ответ, прежде чем он будет отклонен.
Джонатан Леффлер
C работает не так. Это не интерпретируется как попытка вызвать строковый литерал, такой как PHP.
кот