Mengapa C tidak mengizinkan string penggabungan saat menggunakan operator bersyarat?

95

Kode berikut dikompilasi tanpa masalah:

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

Namun, ini tidak dapat dikompilasi:

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

Apa alasan untuk itu?

José D.
sumber
95
Penggabungan string adalah bagian dari fase lexing awal; itu bukan bagian dari ekspresi synatx dari C. Dengan kata lain, tidak ada nilai bertipe "string literal". Sebaliknya, string literal adalah elemen leksikal dari kode sumber yang membentuk nilai.
Kerrek SB
24
Sekadar untuk memperjelas jawaban @KerrekSB - penggabungan string adalah bagian dari pra-pemrosesan teks kode sebelum mengkompilasinya. Sementara operator terner dievaluasi pada waktu proses, setelah kode dikompilasi (atau jika semuanya konstan, hal itu dapat dilakukan dalam waktu kompilasi).
Eugene Sh.
2
Detail: Dalam posting ini, "Hi"dan "Bye"adalah string literal , bukan string seperti yang digunakan di pustaka standar C. Dengan string literal , kompilator akan menggabungkan "H\0i" "B\0ye". Tidak sama dengansprintf(buf,"%s%s", "H\0i" "B\0ye");
chux - Reinstate Monica
15
Kurang lebih alasan yang sama yang tidak dapat Anda lakukana (some_condition ? + : - ) b
user253751
4
Perhatikan bahwa bahkan printf("Hi" ("Bye"));tidak akan berfungsi - tidak memerlukan operator terner; tanda kurung cukup (meskipun printf("Hi" test ? "Bye" : "Goodbye")juga tidak dapat dikompilasi). Hanya ada sejumlah token yang dapat mengikuti literal string. Tanda koma ,, kurung kotak terbuka, kurung kotak [dekat ](seperti dalam 1["abc"]- dan ya, itu mengerikan), kurung siku )dekat, kurung kurawal tutup }(dalam penginisialisasi atau konteks serupa), dan titik koma ;sah (dan string literal lainnya); Saya tidak yakin ada yang lain.
Jonathan Leffler

Jawaban:

121

Menurut Standar C (5.1.1.2 fase Terjemahan)

1 Prioritas di antara aturan sintaks terjemahan ditentukan oleh fase berikut. 6)

  1. Token literal string yang berdekatan digabungkan.

Dan baru setelah itu

  1. Karakter spasi putih yang memisahkan token tidak lagi signifikan. Setiap token pemrosesan awal diubah menjadi token. Token yang dihasilkan dianalisis secara sintaksis dan semantik dan diterjemahkan sebagai unit terjemahan .

Dalam konstruksi ini

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

tidak ada token literal string yang berdekatan. Jadi konstruksi ini tidak valid.

Vlad dari Moskow
sumber
43
Ini hanya mengulangi pernyataan bahwa itu tidak diperbolehkan di C. Itu tidak menjelaskan mengapa , yang merupakan pertanyaannya. Tidak tahu mengapa itu mengumpulkan 26 suara positif dalam 5 jam .... dan terima, tidak kurang! Selamat.
Balapan Ringan di Orbit
4
Harus setuju dengan @LightnessRacesinOrbit di sini. Mengapa tidak (test ? "Bye" : "Goodbye")mengevakuasi ke salah satu string literal yang pada dasarnya membuat "Hi" "Bye" atau "Hi Goodbye"? (pertanyaan saya terjawab di jawaban lain)
Gila
48
@LightnessRacesinOrbit, karena ketika orang biasanya bertanya mengapa sesuatu tidak terkompilasi di C, mereka meminta klarifikasi tentang aturan mana yang dilanggar, bukan mengapa Standards Authors of Antiquity memilihnya seperti itu.
pengguna1717828
4
@LightnessRacesinOrbit Pertanyaan yang Anda gambarkan mungkin di luar topik. Saya tidak dapat melihat alasan teknis mengapa tidak mungkin menerapkannya, jadi tanpa jawaban pasti dari penulis spesifikasi, semua jawaban akan didasarkan pada pendapat. Dan umumnya tidak termasuk dalam kategori pertanyaan "praktis" atau "dapat dijawab" (seperti yang ditunjukkan oleh pusat bantuan yang kami butuhkan).
jpmc26
12
@LightnessRacesinOrbit Ini menjelaskan alasannya : "karena standar C mengatakan demikian". Pertanyaan tentang mengapa aturan ini didefinisikan seperti yang didefinisikan akan berada di luar topik.
pengguna11153
135

Sesuai standar C11, bab §5.1.1.2, rangkaian literal string yang berdekatan:

Token literal string yang berdekatan digabungkan.

terjadi dalam fase penerjemahan . Di samping itu:

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

melibatkan operator bersyarat, yang dievaluasi pada waktu proses . Jadi, pada waktu kompilasi, selama fase penerjemahan, tidak ada literal string yang berdekatan, sehingga penggabungan tidak mungkin dilakukan. Sintaksnya tidak valid dan karenanya dilaporkan oleh compiler Anda.


Untuk menguraikan sedikit tentang bagian why , selama fase preprocessing, literal string yang berdekatan digabungkan dan direpresentasikan sebagai literal string tunggal (token). Penyimpanan dialokasikan sesuai dan literal string yang digabungkan dianggap sebagai entitas tunggal (satu string literal).

Di sisi lain, dalam kasus run-time concatenation, tujuan harus memiliki cukup memori untuk menampung string literal yang digabungkan jika tidak, tidak akan ada cara agar output gabungan yang diharapkan dapat diakses. Sekarang, dalam kasus literal string , mereka sudah dialokasikan memori pada waktu kompilasi dan tidak dapat diperpanjang agar sesuai dengan masukan yang masuk atau ditambahkan ke konten asli. Dengan kata lain, tidak mungkin hasil gabungan dapat diakses (disajikan) sebagai literal string tunggal . Jadi, konstruksi ini secara inheren salah.

Sebagai informasi, untuk rangkaian run-time ( bukan literal ), kami memiliki fungsi library strcat()yang menggabungkan dua string . Perhatikan, deskripsi menyebutkan:

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

The strcat()Fungsi menambahkan salinan string yang ditunjukkan oleh s2(termasuk terminating nol karakter) sampai akhir dari string yang ditunjukkan olehs1 . Karakter awal s2menimpa karakter nol di akhir s1. [...]

Jadi, kita bisa lihat, itu s1adalah string , bukan string literal . Namun, karena konten s2tidak diubah dengan cara apa pun, ini bisa menjadi string literal .

Sourav Ghosh
sumber
1
Anda mungkin ingin menambahkan penjelasan tambahan tentang strcat: larik tujuan harus cukup panjang untuk menerima karakter dari s2ditambah terminator null setelah karakter sudah ada di sana.
chqrlie
39

Rangkaian literal string dilakukan oleh preprocessor pada waktu kompilasi. Tidak ada cara untuk penggabungan ini untuk mengetahui nilai dari test, yang tidak diketahui sampai program benar-benar dijalankan. Oleh karena itu, literal string ini tidak dapat digabungkan.

Karena kasus umumnya adalah Anda tidak akan memiliki konstruksi seperti ini untuk nilai yang diketahui pada waktu kompilasi, standar C dirancang untuk membatasi fitur penggabungan otomatis ke kasus yang paling dasar: ketika literal benar-benar tepat berdampingan satu sama lain .

Tetapi bahkan jika itu tidak menyebutkan batasan ini dengan cara itu, atau jika batasan itu dibangun secara berbeda, contoh Anda masih tidak mungkin untuk diwujudkan tanpa membuat penggabungan sebagai proses runtime. Dan, untuk itu, kami memiliki fungsi perpustakaan seperti strcat.

Balapan Ringan dalam Orbit
sumber
3
Saya baru saja membaca asumsi. Meskipun apa yang Anda katakan cukup valid, Anda tidak dapat memberikan sumbernya karena tidak ada. Satu-satunya sumber berkenaan dengan C adalah dokumen standar yang (meskipun dalam banyak kasus obvisious) tidak menyatakan mengapa beberapa hal seperti itu tetapi hanya menyatakan bahwa mereka harus sedemikian spesifik. Jadi bersikap rewel tentang Vlad dari jawaban Moskow itu tidak masuk akal. Karena OP dapat diuraikan menjadi "Mengapa seperti itu?" -Dimana satu-satunya jawaban yang bersumber benar adalah "Karena ini adalah C, dan begitulah C didefinisikan" itulah satu-satunya jawaban yang benar secara literar.
dhein
1
Ini (diakui) kurang penjelasannya. Tapi di sini sekali lagi dikatakan adalah jawaban Vlad melayani lebih banyak sebagai penjelasan untuk masalah inti daripada masalah Anda. Sekali lagi berkata: Meskipun informasi yang Anda berikan dapat saya konfirmasikan terkait dan benar, saya tidak setuju dengan keluhan Anda. dan sementara saya tidak akan menganggap Anda offtopic juga, itu dari POV saya lebih offtopic daripada Vlads sebenarnya.
dhein
11
@Zaibis: Sumbernya adalah saya. Jawaban Vlad bukanlah penjelasan sama sekali; itu hanyalah konfirmasi dari premis pertanyaan. Tentunya tidak satu pun dari mereka yang "di luar topik" (Anda mungkin ingin mencari tahu apa arti istilah itu). Tapi Anda berhak atas pendapat Anda.
Balapan Ringan di Orbit
Bahkan setelah membaca komentar di atas, saya masih bertanya-tanya siapa yang memberi suara negatif pada jawaban ini ᶘ ᵒᴥᵒᶅ Saya yakin ini adalah jawaban yang sempurna kecuali OP meminta klarifikasi lebih lanjut tentang jawaban ini.
Mohit Jain
2
Saya tidak dapat membedakan mengapa jawaban ini dapat diterima oleh Anda dan @VladfromMoscow tidak, ketika keduanya mengatakan hal yang sama, dan ketika jawabannya didukung oleh kutipan dan milik Anda tidak.
Marquis dari Lorne
30

Karena C tidak memiliki stringtipe. Literal string dikompilasi menjadi chararray, direferensikan oleh char*pointer.

C memungkinkan literal yang berdekatan untuk digabungkan pada waktu kompilasi , seperti pada contoh pertama Anda. Kompilator C sendiri memiliki beberapa pengetahuan tentang string. Tetapi informasi ini tidak ada pada waktu proses, dan karenanya penggabungan tidak dapat terjadi.

Selama proses kompilasi, contoh pertama Anda "diterjemahkan" ke:

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

Perhatikan bagaimana dua string digabungkan menjadi satu array statis oleh kompilator, sebelum program dijalankan.

Namun, contoh kedua Anda "diterjemahkan" menjadi seperti ini:

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

Harus jelas mengapa ini tidak dapat dikompilasi. Operator terner ?dievaluasi pada waktu proses, bukan waktu kompilasi, ketika "string" tidak lagi ada, tetapi hanya sebagai chararray sederhana , yang direferensikan oleh char*pointer. Tidak seperti literal string yang berdekatan , pointer char yang berdekatan hanyalah kesalahan sintaksis.

Tidak ditandatangani
sumber
2
Jawaban yang sangat bagus, mungkin yang terbaik di sini. "Harus jelas mengapa ini tidak bisa dikompilasi." Anda dapat mempertimbangkan untuk memperluasnya dengan "karena operator terner adalah bersyarat yang dievaluasi pada waktu proses bukan waktu kompilasi ".
kucing
Seharusnya tidak static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};menjadi static const char *char_ptr_1 = "HiBye";dan juga untuk sisa pointer?
Spikatrix
@CoolGuy Ketika Anda menulis static const char *char_ptr_1 = "HiBye";kompilator menerjemahkan baris ke static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};, jadi tidak, itu tidak boleh ditulis "seperti string". Seperti yang dikatakan Answer, string dikompilasi menjadi array karakter, dan jika Anda menetapkan array karakter dalam bentuk paling "mentah", Anda akan menggunakan daftar karakter yang dipisahkan koma, sepertistatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
Ankush
3
@AndaTahu Ya. Tetapi meskipun static const char str[] = {'t', 'e', 's', 't', '\0'};sama static const char str[] = "test";, static const char* ptr = "test";adalah tidak sama dengan static const char* ptr = {'t', 'e', 's', 't', '\0'};. Yang pertama valid dan akan dikompilasi tetapi yang terakhir tidak valid dan melakukan apa yang Anda harapkan.
Spikatrix
Saya telah menyempurnakan paragraf terakhir dan mengoreksi contoh kodenya, terima kasih!
ditandatangani
13

Jika Anda benar-benar ingin kedua cabang menghasilkan konstanta string waktu kompilasi untuk dipilih saat runtime, Anda memerlukan makro.

#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;
}
Eric
sumber
10

Apa alasan untuk itu?

Kode Anda menggunakan operator terner secara bersyarat memilih antara dua literal string. Terlepas dari kondisi yang diketahui atau tidak diketahui, ini tidak dapat dievaluasi pada waktu kompilasi, sehingga tidak dapat dikompilasi. Bahkan pernyataan ini printf("Hi" (1 ? "Bye" : "Goodbye"));tidak dapat dikompilasi. Alasannya dijelaskan secara mendalam pada jawaban di atas. Kemungkinan lain untuk membuat pernyataan seperti itu menggunakan operator terner valid untuk dikompilasi , juga akan melibatkan tag format dan hasil dari pernyataan operator terner diformat sebagai argumen tambahan untuk printf. Meski begitu, printf()hasil cetak akan memberikan kesan "telah menggabungkan" string tersebut hanya pada, dan sedini runtime .

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}
pengguna3078414
sumber
3
SO bukan situs Tutorial. Anda harus memberikan Jawaban ke OP dan bukan tutorial.
Michi
1
Ini tidak menjawab pertanyaan OP. Ini mungkin upaya untuk menyelesaikan masalah mendasar OP, tetapi kami tidak benar-benar tahu apa itu.
Keith Thompson
1
printftidak membutuhkan penentu format; jika hanya penggabungan dilakukan pada waktu kompilasi (yang sebenarnya tidak), penggunaan printf oleh OP akan valid.
David Conrad
Terima kasih atas komentar Anda, @David Conrad. Kata-kata saya yang ceroboh memang akan membuat seolah-olah menyatakan printf()akan membutuhkan tag format, yang sama sekali tidak benar. Diperbaiki!
pengguna3078414
Itu kata-kata yang lebih baik. +1 Terima kasih.
David Conrad
7

Dalam printf("Hi" "Bye");Anda memiliki dua array berturut-turut char yang kompilator dapat membuat menjadi array tunggal.

Di printf("Hi" (test ? "Bye" : "Goodbye"));Anda memiliki satu array diikuti oleh pointer ke char (array diubah menjadi pointer ke elemen pertamanya). Kompiler tidak dapat menggabungkan array dan pointer.

pmg
sumber
0

Untuk menjawab pertanyaan - saya akan pergi ke definisi printf. Fungsi printf mengharapkan const char * sebagai argumen. Setiap string literal seperti "Hi" adalah sebuah const char *; namun ekspresi seperti (test)? "str1" : "str2"BUKAN sebuah const char * karena hasil dari ekspresi tersebut hanya ditemukan pada saat run-time dan oleh karena itu tidak dapat ditentukan pada waktu kompilasi, sebuah fakta yang menyebabkan compiler mengeluh. Di sisi lain - ini bekerja dengan baikprintf("hi %s", test? "yes":"no")

Stats_Lover
sumber
* Namun ungkapan seperti (test)? "str1" : "str2"BUKAN const char*... Tentu saja! Ini bukan ekspresi konstan, tetapi tipenya adalah const char * . Tidak masalah untuk menulis printf(test ? "hi " "yes" : "hi " "no"). Masalah OP tidak ada hubungannya dengan printf, "Hi" (test ? "Bye" : "Goodbye")adalah kesalahan sintaks tidak peduli apa konteks ekspresi.
chqrlie
Sepakat. Saya bingung keluaran ekspresi dengan ekspresi itu sendiri
Stats_Lover
-4

Ini tidak dapat dikompilasi karena daftar parameter untuk fungsi printf adalah

(const char *format, ...)

dan

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

tidak sesuai dengan daftar parameter.

gcc mencoba memahaminya dengan membayangkannya

(test ? "Bye" : "Goodbye")

adalah daftar parameter, dan mengeluh bahwa "Hi" bukanlah sebuah fungsi.

Rodbots
sumber
6
Selamat datang di Stack Overflow. Anda benar bahwa itu tidak cocok dengan printf()daftar argumen, tetapi itu karena ekspresi tidak valid di mana pun - tidak hanya dalam printf()daftar argumen. Dengan kata lain, Anda telah memilih alasan yang terlalu khusus untuk masalah tersebut; masalah umumnya "Hi" (adalah tidak valid di C, apalagi di panggilan ke printf(). Saya sarankan Anda menghapus jawaban ini sebelum ditolak.
Jonathan Leffler
Itu bukanlah cara C bekerja. Ini tidak diuraikan sebagai mencoba memanggil string literal seperti PHP.
kucing