โค้ดต่อไปนี้คอมไพล์ได้โดยไม่มีปัญหา:
int main() {
printf("Hi" "Bye");
}
อย่างไรก็ตามสิ่งนี้ไม่ได้รวบรวม:
int main() {
int test = 0;
printf("Hi" (test ? "Bye" : "Goodbye"));
}
เหตุผลนั้นคืออะไร?
โค้ดต่อไปนี้คอมไพล์ได้โดยไม่มีปัญหา:
int main() {
printf("Hi" "Bye");
}
อย่างไรก็ตามสิ่งนี้ไม่ได้รวบรวม:
int main() {
int test = 0;
printf("Hi" (test ? "Bye" : "Goodbye"));
}
เหตุผลนั้นคืออะไร?
"Hi"
และ"Bye"
เป็นตัวอักษรสตริงไม่ใช่สตริงที่ใช้ในไลบรารีมาตรฐาน C ด้วยสตริงลิเทอรัลคอมไพเลอร์จะเชื่อมต่อกัน"H\0i" "B\0ye"
concatenate ไม่เหมือนกันกับsprintf(buf,"%s%s", "H\0i" "B\0ye");
a (some_condition ? + : - ) b
printf("Hi" ("Bye"));
จะใช้ไม่ได้ - ไม่จำเป็นต้องใช้ตัวดำเนินการที่เกี่ยวข้อง วงเล็บก็เพียงพอแล้ว (แม้ว่าprintf("Hi" test ? "Bye" : "Goodbye")
จะไม่รวบรวมก็ตาม) มีโทเค็นจำนวน จำกัด เท่านั้นที่สามารถติดตามสตริงลิเทอรัลได้ ลูกน้ำ,
, วงเล็บเหลี่ยมเปิด, วงเล็บเหลี่ยม[
ปิด]
(เช่นเดียวกับ1["abc"]
- และใช่มันน่ากลัว), วงเล็บกลม)
ปิด, วงเล็บปีกกาปิด}
(ในตัวเริ่มต้นหรือบริบทที่คล้ายกัน) และอัฒภาค;
ถูกต้อง (และอีกสตริงตามตัวอักษร); ฉันไม่แน่ใจว่ามีคนอื่น ๆ
คำตอบ:
ตามมาตรฐาน C (ขั้นตอนการแปล 5.1.1.2)
1 ลำดับความสำคัญของกฎไวยากรณ์ของการแปลถูกระบุโดยขั้นตอนต่อไปนี้ 6)
- โทเค็นลิเทอรัลสตริงที่อยู่ติดกันจะเชื่อมต่อกัน
และหลังจากนั้น
- อักขระเว้นวรรคสีขาวที่คั่นโทเค็นไม่มีความสำคัญอีกต่อไป โทเค็นก่อนการประมวลผลแต่ละรายการจะถูกแปลงเป็นโทเค็นราชสกุลส่งผลให้มีการไวยากรณ์และความหมายการวิเคราะห์และแปลเป็นหน่วยการแปล
ในการก่อสร้างครั้งนี้
"Hi" (test ? "Bye" : "Goodbye")
ไม่มีโทเค็นตัวอักษรสตริงที่อยู่ติดกัน ดังนั้นการก่อสร้างนี้ไม่ถูกต้อง
(test ? "Bye" : "Goodbye")
evaulate อย่างใดอย่างหนึ่งของสายอักขระตัวอักษรเป็นหลักทำ "Hi" "Bye"
หรือ"Hi Goodbye"
? (คำถามของฉันมีคำตอบอยู่ในคำตอบอื่น ๆ )
ตามมาตรฐาน 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
เขียนทับอักขระ nulls1
ในตอนท้ายของ [... ]
เราจะเห็นว่าs1
เป็นสตริงไม่ใช่สตริงลิเทอรัล อย่างไรก็ตามเนื่องจากเนื้อหาของs2
ไม่มีการเปลี่ยนแปลง แต่อย่างใดจึงสามารถเป็นสตริงตามตัวอักษรได้เป็นอย่างดี
strcat
อาร์เรย์ปลายทางต้องมีความยาวเพียงพอที่จะรับอักขระจากs2
บวกเทอร์มิเนเตอร์ว่างหลังจากที่มีอักขระอยู่แล้ว
การต่อสายอักขระตามตัวอักษรถูกดำเนินการโดยตัวประมวลผลล่วงหน้าในเวลาคอมไพล์ ไม่มีทางที่การเชื่อมต่อนี้จะรับรู้ถึงค่าของtest
ซึ่งไม่มีใครรู้จนกว่าโปรแกรมจะดำเนินการจริง ดังนั้นจึงไม่สามารถต่อสายอักขระเหล่านี้ได้
เนื่องจากกรณีทั่วไปคือคุณจะไม่มีโครงสร้างเช่นนี้สำหรับค่าที่ทราบในเวลาคอมไพล์มาตรฐาน C ได้รับการออกแบบมาเพื่อ จำกัด คุณลักษณะการเรียงต่อกันอัตโนมัติเป็นกรณีพื้นฐานที่สุด: เมื่อตัวอักษรอยู่ข้างกันอย่างแท้จริง .
แต่แม้ว่าจะไม่ได้ระบุถึงข้อ จำกัด นี้ในลักษณะนั้นหรือหากข้อ จำกัด นั้นสร้างขึ้นแตกต่างกันตัวอย่างของคุณก็ยังคงเป็นไปไม่ได้ที่จะตระหนักได้หากไม่ทำให้การเชื่อมต่อเป็นกระบวนการรันไทม์ และสำหรับสิ่งนั้นเรามีฟังก์ชั่นไลบรารีเช่นstrcat
.
เพราะ 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));
}
ควรมีความชัดเจนว่าเหตุใดจึงไม่รวบรวมข้อมูลนี้ ตัวดำเนินการ ternary ?
จะได้รับการประเมินที่รันไทม์ไม่ใช่เวลาคอมไพล์เมื่อ "สตริง" ไม่มีอยู่อีกต่อไป แต่เป็นchar
อาร์เรย์ธรรมดาเท่านั้นที่อ้างอิงโดยพchar*
อยน์เตอร์ ไม่เหมือนกับตัวอักษรสตริงที่อยู่ติดกันตัวชี้อักขระที่อยู่ติดกันเป็นเพียงข้อผิดพลาดทางไวยากรณ์
static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
เป็นstatic const char *char_ptr_1 = "HiBye";
และในทำนองเดียวกันสำหรับตัวชี้ที่เหลือ?
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'};
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'};
เช่นเดียวกับ อดีตถูกต้องและจะคอมไพล์ แต่ส่วนหลังไม่ถูกต้องและทำในสิ่งที่คุณคาดหวัง
หากคุณต้องการให้ทั้งสองสาขาสร้างค่าคงที่สตริงเวลาคอมไพล์เพื่อเลือกในรันไทม์คุณจะต้องมีมาโคร
#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;
}
เหตุผลนั้นคืออะไร?
รหัสของคุณโดยใช้ตัวดำเนินการ ternary จะเลือกตามเงื่อนไขระหว่างตัวอักษรสตริงสองตัว ไม่ว่าเงื่อนไขจะทราบหรือไม่ทราบก็ไม่สามารถประเมินได้ในเวลาคอมไพล์ดังนั้นจึงไม่สามารถคอมไพล์ได้ แม้แต่คำสั่งนี้printf("Hi" (1 ? "Bye" : "Goodbye"));
ก็ไม่สามารถรวบรวมได้ เหตุผลในเชิงลึกอธิบายไว้ในคำตอบข้างต้น ความเป็นไปได้อีกอย่างหนึ่งของการทำคำสั่งดังกล่าวโดยใช้ผู้ประกอบการที่ถูกต้องประกอบไปด้วยการรวบรวมยังจะเกี่ยวข้องกับแท็กรูปแบบและผลของคำสั่งผู้ประกอบ ternary ในรูปแบบอาร์กิวเมนต์เพิ่มเติมprintf
เพื่อ แม้แล้วprintf()
พิมพ์ออกมาจะให้เป็นที่ประทับใจของ "ที่มีการตัดแบ่ง" สตริงเหล่านั้นเท่านั้นที่และเร็วที่สุดเท่าที่รันไทม์
#include <stdio.h>
int main() {
int test = 0;
printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}
printf
ไม่ต้องการตัวระบุรูปแบบ ถ้าเฉพาะการเรียงต่อกันเสร็จสิ้นในเวลาคอมไพล์ (ซึ่งไม่ใช่) การใช้ printf ของ OP จะใช้ได้
printf()
จะต้องใช้แท็กรูปแบบซึ่งไม่เป็นความจริงอย่างแน่นอน แก้ไขแล้ว!
ในprintf("Hi" "Bye");
คุณมีสองอาร์เรย์ติดต่อกันของถ่านซึ่งคอมไพเลอร์สามารถทำให้เป็นอาร์เรย์เดียว
ในprintf("Hi" (test ? "Bye" : "Goodbye"));
คุณมีอาร์เรย์หนึ่งรายการตามด้วยตัวชี้ไปยังถ่าน (อาร์เรย์ที่แปลงเป็นตัวชี้ไปยังองค์ประกอบแรก) คอมไพลเลอร์ไม่สามารถผสานอาร์เรย์และตัวชี้ได้
เพื่อตอบคำถาม - ฉันจะไปที่คำจำกัดความของ printf ฟังก์ชัน printf คาดว่าconst char *เป็นอาร์กิวเมนต์ ตัวอักษรสตริงใด ๆ เช่น "Hi" คือ 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")
เป็นข้อผิดพลาดทางไวยากรณ์ไม่ว่าสิ่งที่บริบทการแสดงออกเป็น
สิ่งนี้ไม่รวบรวมเนื่องจากรายการพารามิเตอร์สำหรับฟังก์ชัน printf คือ
(const char *format, ...)
และ
("Hi" (test ? "Bye" : "Goodbye"))
ไม่พอดีกับรายการพารามิเตอร์
gcc พยายามทำให้เข้าใจโดยจินตนาการว่า
(test ? "Bye" : "Goodbye")
เป็นรายการพารามิเตอร์และบ่นว่า "Hi" ไม่ใช่ฟังก์ชัน
printf()
รายการอาร์กิวเมนต์ แต่นั่นเป็นเพราะนิพจน์ไม่ถูกต้องทุกที่ไม่ใช่แค่ในprintf()
รายการอาร์กิวเมนต์ กล่าวอีกนัยหนึ่งคุณได้เลือกเหตุผลที่เฉพาะเจาะจงเกินไปสำหรับปัญหานี้ ปัญหาทั่วไปคือการที่"Hi" (
ไม่ถูกต้องใน C printf()
ให้อยู่คนเดียวในการเรียกไปยัง ฉันขอแนะนำให้คุณลบคำตอบนี้ก่อนที่จะมีการโหวตลดลง