Чому C не дозволяє об'єднувати рядки при використанні умовного оператора?


95

Наступний код компілюється без проблем:

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

Однак це не компілює:

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

У чому причина цього?


95
Конкатенація рядків є частиною ранньої лексичної фази; це не є частиною виразу synatx C. Іншими словами, немає значення типу "string literal". Швидше, рядкові літерали - це лексичні елементи вихідного коду, які формують значення.
Керрек СБ

24
Тільки для уточнення відповіді @KerrekSB - об’єднання рядків є частиною попередньої обробки тексту коду перед його компіляцією. Поки тернарний оператор обчислюється під час виконання, після компіляції коду (або, якщо все постійно, це можна зробити під час компіляції).
Євген Ш.

2
Детально: У цій публікації "Hi"і "Bye"є рядкові літерали , а не рядки, як це використовується в стандартній бібліотеці C. За допомогою рядкових літералів компілятор об’єднається "H\0i" "B\0ye". Не те саме зsprintf(buf,"%s%s", "H\0i" "B\0ye");
chux

15
Більш-менш та сама причина, через яку ви не можете цього зробитиa (some_condition ? + : - ) b
user253751

4
Зверніть увагу, що навіть printf("Hi" ("Bye"));не буде працювати - для цього не потрібен тернарний оператор; дужок достатньо (хоча printf("Hi" test ? "Bye" : "Goodbye")також не компілюється). Існує лише обмежена кількість лексем, які можуть слідувати за рядковим літералом. Кома ,, відкрита квадратна дужка [, закрита квадратна дужка ](як у 1["abc"]- і так, це жахливо), закрита кругла дужка ), закрита фігурна дужка }(в ініціалізаторі або подібному контексті) та крапка з комою ;є допустимими (і інший буквальний рядок); Я не впевнений, що є інші.
Джонатан Леффлер,

Відповіді:


121

Відповідно до стандарту С (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"? (на моє запитання відповідають інші відповіді)
Божевільний

48
@LightnessRacesinOrbit, тому що коли люди зазвичай запитують, чому щось не компілюється на мові C, вони просять пояснити, яке правило воно порушує, а не чому Автори Стародавніх стандартів вибрали це саме так.
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файлу жодним чином не змінюється, він цілком може бути рядковим літералом .


можливо, ви захочете додати додаткове пояснення з приводу strcat: цільовий масив повинен бути достатньо довгим, щоб приймати символи s2плюс нульовий термінатор після вже наявних там символів.
chqrlie

39

Конкатенація рядкового літералу виконується препроцесором під час компіляції. Ця конкатенація не може усвідомити цінністьtest , що невідомо, доки програма фактично не виконується. Отже, ці рядкові літерали не можна об’єднати.

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

Але навіть якби це обмеження не було сформульовано таким чином, або якщо обмеження було побудовано інакше, ваш приклад все одно було б неможливо реалізувати, не зробивши конкатенацію процесом виконання. І для цього ми маємо такі функції бібліотеки, як strcat.


3
Я просто читаю припущення. Хоча те, що ви говорите, є в значній мірі дійсним, ви не можете надати джерела для цього, оскільки таких немає. Єдиним джерелом стосовно С є стандартний документ, який (хоча він у багатьох випадках є невидимим) не вказує, чому деякі речі є такими, якими вони є, а лише стверджує, що вони повинні бути саме таким. Тож вважати цю прискіпливу думку щодо Влада з московської відповіді непристойно. Оскільки OP можна розбити на "Чому це так?" - Де єдиною правильною відповіддю є "Оскільки це C, і саме так визначається C", це єдина буквально пряма правильна відповідь.
dhein

1
Це (визнано) не має пояснень. Але тут знову ж таки сказано, що відповідь Влада набагато більше пояснює основну проблему, ніж ваша. Ще раз сказав: Хоча інформація, яку ви надаєте, я можу підтвердити, пов’язана та правильна, я не погоджуюся з вашими скаргами. і хоча я б не вважав вас також офтопіком, це з мого POV більш офтопік, ніж насправді Влад.
dhein

11
@Zaibis: Джерело - це я. Відповідь Влада - це зовсім не пояснення; це лише підтвердження передумови питання. Звичайно, жоден з них не є "не в темі" (можливо, ви захочете знайти, що означає цей термін). Але ви маєте право на свою думку.
Гонки легкості на орбіті

Навіть прочитавши вищезазначені коментарі, я все ще дивуюсь, хто голосував за цю відповідь ᶘ ᵒᴥᵒᶅ Я вважаю, що це ідеальна відповідь, якщо ОП не запитає подальших роз’яснень щодо цієї відповіді.
Мохіт Джейн

2
Я не можу розрізнити, чому ця відповідь є прийнятною для вас, а @ VladfromMoscow - ні, коли вони обидва говорять одне і те ж, а коли його підтримка цитується, а ваша ні.
Маркіз Лорнський,

30

Оскільки С не має stringтипу. Рядкові літерали компілюються в charмасиви, на які посилається char*вказівник.

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
@Ankush Так. Але хоча 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

Я розробив останній абзац і виправив приклади коду, дякую!
Без підпису

12

Якщо ви дійсно хочете, щоб обидві гілки виробляли рядкові константи часу компіляції для вибору під час виконання, вам знадобиться макрос.

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

3
SO не є підручником. Ви повинні дати відповідь на ОП, а не підручник.
Мічі

1
Це не відповідає на запитання ОП. Це може бути спробою вирішити основну проблему ОП, але ми насправді не знаємо, що це таке.
Кіт Томпсон,

1
printfне вимагає специфікатора формату; якби лише конкатенація була зроблена під час компіляції (а це не так), OP використовує printf.
Девід Конрад

Дякую за ваше зауваження, @David Conrad. Моє недбале формулювання справді виглядало б так, ніби для заявлення printf()потрібен тег формату, що абсолютно не відповідає дійсності. Виправлено!
user3078414

Це краще формулювання. +1 Дякую.
Девід Конрад

7

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

У printf("Hi" (test ? "Bye" : "Goodbye"));вас є один масив, за яким слідує покажчик на char (масив, перетворений на покажчик на свій перший елемент). Компілятор не може об'єднати масив і покажчик.


0

Щоб відповісти на питання - я б перейшов до визначення printf. Функція printf очікує const char * як аргумент. Будь-який рядковий літерал, такий як "Привіт", є const char *; однак такий вираз, (test)? "str1" : "str2"який НЕ є const char *, оскільки результат такого виразу знайдений лише під час виконання, а отже, невизначений під час компіляції, факт, який належним чином викликає скаргу компілятора. З іншого боку - це чудово працюєprintf("hi %s", test? "yes":"no")


* однак вираз типу (test)? "str1" : "str2"НЕ є const char*... Звичайно, що є! Це не постійний вираз, але його тип є const char * . Було б чудово писати printf(test ? "hi " "yes" : "hi " "no"). Проблема OP не має нічого спільного printf, "Hi" (test ? "Bye" : "Goodbye")це синтаксична помилка, незалежно від того, який контекст виразу.
chqrlie

Домовились. Я переплутав висновок виразу з самим виразом
Stats_Lover

-4

Це не компілюється, оскільки список параметрів для функції printf є

(const char *format, ...)

і

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

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

gcc намагається зрозуміти це, уявляючи це

(test ? "Bye" : "Goodbye")

є списком параметрів і скаржиться, що "Привіт" не є функцією.


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

Це не так, як працює C. Це не аналізується як спроба викликати такий рядковий літерал, як PHP.
кішка
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.