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

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

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

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

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

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

Ответ 1

В соответствии со стандартом C (5.1.1.2 этапы перевода)

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

  1. Связанные токены строковых литералов конкатенированы.

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

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

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

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

нет соседних строковых литералов. Поэтому эта конструкция недействительна.

Ответ 2

В соответствии со стандартом 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 не изменяется каким-либо образом, оно вполне может быть строковым литералом.

Ответ 3

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

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

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

Ответ 4

Потому что 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*. В отличие от соседних строковых литералов, смежные указатели char являются просто синтаксической ошибкой.

Ответ 5

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

#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;
}

Ответ 6

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

Ваш код с использованием тернарного оператора условно выбирает между двумя строковыми литералами. Независимо от известного или неизвестного состояния, это невозможно оценить во время компиляции, поэтому он не может скомпилироваться. Даже это утверждение 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
}

Ответ 7

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

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

Ответ 8

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

Ответ 9

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

(const char *format, ...)

и

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

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

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

(test ? "Bye" : "Goodbye")

является списком параметров и жалуется, что "Привет" не является функцией.