Tại sao C không cho phép nối các chuỗi khi sử dụng toán tử điều kiện?


95

Mã sau đây biên dịch mà không gặp sự cố:

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

Tuy nhiên, điều này không biên dịch:

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

lý do cho điều đó là gì?


95
Nối chuỗi là một phần của giai đoạn ghép nối đầu tiên; nó không phải là một phần của biểu thức synatx của C. Nói cách khác, không có giá trị của kiểu "chuỗi ký tự". Đúng hơn, các ký tự chuỗi là các phần tử từ vựng của mã nguồn tạo thành các giá trị.
Kerrek SB

24
Chỉ để làm rõ câu trả lời @KerrekSB - việc nối các chuỗi là một phần của quá trình xử lý trước văn bản mã trước khi biên dịch nó. Trong khi toán tử bậc ba được đánh giá trong thời gian chạy, sau khi mã được biên dịch (hoặc trong trường hợp mọi thứ không đổi, nó có thể được thực hiện trong thời gian biên dịch).
Eugene Sh.

2
Chi tiết: Trong bài đăng này, "Hi""Bye"chuỗi ký tự , không phải chuỗi được sử dụng trong thư viện tiêu chuẩn C. Với chuỗi ký tự , trình biên dịch sẽ nối "H\0i" "B\0ye". Không giống vớisprintf(buf,"%s%s", "H\0i" "B\0ye");
chux - Phục hồi Monica

15
Nhiều hơn hoặc ít hơn các lý do tương tự bạn không thể làma (some_condition ? + : - ) b
user253751

4
Lưu ý rằng thậm chí printf("Hi" ("Bye"));sẽ không hoạt động - nó không yêu cầu toán tử bậc ba; dấu ngoặc là đủ (mặc dù printf("Hi" test ? "Bye" : "Goodbye")cũng sẽ không biên dịch). Chỉ có một số lượng giới hạn mã thông báo có thể theo sau một chuỗi ký tự. Dấu phẩy ,, dấu ngoặc vuông mở, dấu ngoặc vuông [đóng ](như trong 1["abc"]- và vâng, thật khủng khiếp), dấu ngoặc tròn )đóng, dấu ngoặc nhọn đóng }(trong bộ khởi tạo hoặc ngữ cảnh tương tự) và dấu chấm phẩy ;là hợp lệ (và một chuỗi ký tự khác); Tôi không chắc có bất kỳ người khác.
Jonathan Leffler

Câu trả lời:


121

Theo Tiêu chuẩn C (5.1.1.2 Các giai đoạn dịch)

1 Mức độ ưu tiên trong số các quy tắc cú pháp của bản dịch được quy định bởi các giai đoạn sau. 6)

  1. Các mã thông báo theo chuỗi liền kề được nối với nhau.

Và chỉ sau đó

  1. Các ký tự khoảng trắng phân tách mã thông báo không còn quan trọng nữa. Mỗi mã thông báo tiền xử lý được chuyển đổi thành mã thông báo. Các mã thông báo kết quả được phân tích cú pháp và ngữ nghĩa và được dịch như một đơn vị dịch .

Trong công trình này

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

không có mã thông báo chuỗi ký tự liền kề. Vì vậy việc xây dựng này là không hợp lệ.


43
Điều này chỉ lặp lại khẳng định rằng nó không được phép ở C. Nó không giải thích tại sao , đó là câu hỏi. Không biết tại sao nó tích lũy được 26 lượt bình chọn trong 5 giờ .... và lượt chấp nhận, không ít! Xin chúc mừng.
Lightness Races in Orbit

4
Phải đồng ý với @LightnessRacesinOrbit ở đây. Tại sao không nên (test ? "Bye" : "Goodbye")chuyển sang một trong hai chuỗi ký tự về cơ bản tạo ra "Hi" "Bye" hoặc "Hi Goodbye"? (câu hỏi của tôi được trả lời trong các câu trả lời khác)
Mất trí

48
@LightnessRacesinOrbit, bởi vì khi mọi người thường hỏi tại sao một cái gì đó không được biên dịch trong C, họ sẽ yêu cầu làm rõ về quy tắc nào mà nó vi phạm, chứ không phải tại sao các tác giả tiêu chuẩn của Antiquity lại chọn nó theo cách đó.
user1717828

4
@LightnessRacesinOrbit Câu hỏi bạn mô tả có thể sẽ lạc đề. Tôi không thể thấy bất kỳ lý do kỹ thuật nào tại sao không thể thực hiện điều đó, vì vậy nếu không có câu trả lời dứt khoát từ các tác giả của thông số kỹ thuật, tất cả các câu trả lời sẽ dựa trên ý kiến. Và nó thường sẽ không thuộc loại câu hỏi "thực tế" hoặc "trả lời được" (như trung tâm trợ giúp cho biết chúng tôi yêu cầu).
jpmc26

12
@LightnessRacesinOrbit Nó giải thích tại sao : "bởi vì tiêu chuẩn C nói như vậy". Câu hỏi về lý do tại sao quy tắc này được xác định như đã định nghĩa sẽ bị lạc đề.
user11153

135

Theo tiêu chuẩn C11, chương §5.1.1.2, nối các ký tự chuỗi liền kề:

Các mã thông báo theo chuỗi liền kề được nối với nhau.

xảy ra trong giai đoạn dịch mã . Mặt khác:

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

liên quan đến toán tử điều kiện, được đánh giá tại thời điểm chạy . Vì vậy, tại thời điểm biên dịch, trong giai đoạn dịch, không có các ký tự chuỗi liền kề nào hiện diện, do đó việc nối không thể thực hiện được. Cú pháp không hợp lệ và do đó trình biên dịch của bạn báo cáo.


Để giải thích một chút về phần lý do , trong giai đoạn tiền xử lý, các ký tự chuỗi liền kề được nối và biểu diễn dưới dạng một ký tự chuỗi đơn (mã thông báo). Bộ nhớ được phân bổ tương ứng và chuỗi ký tự nối được coi là một thực thể duy nhất (một chuỗi ký tự).

Mặt khác, trong trường hợp nối thời gian chạy, đích phải có đủ bộ nhớ để chứa chuỗi được nối theo nghĩa đen , nếu không sẽ không có cách nào có thể truy cập đầu ra nối được mong đợi . Bây giờ, trong trường hợp chuỗi ký tự , chúng đã được cấp phát bộ nhớ tại thời điểm biên dịch và không thể mở rộng để phù hợp với bất kỳ đầu vào nào nữa vào hoặc nối vào nội dung gốc. Nói cách khác, sẽ không có cách nào mà kết quả được nối có thể được truy cập (trình bày) dưới dạng một chuỗi ký tự đơn . Vì vậy, cấu trúc này vốn không chính xác.

Chỉ FYI, đối với nối chuỗi thời gian chạy ( không phải theo nghĩa đen ), chúng ta có hàm thư viện strcat()nối hai chuỗi . Lưu ý, mô tả đề cập đến:

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

Các strcat()chức năng gắn thêm một bản sao của chuỗi được trỏ đến bởi s2(bao gồm các ký tự null chấm dứt) để cuối cùng của chuỗi được trỏ đến bởis1 . Ký tự ban đầu của sẽ s2ghi đè lên ký tự null ở cuối s1. [...]

Vì vậy, chúng ta có thể thấy, chuỗis1 là một chuỗi , không phải là một chuỗi theo nghĩa đen . Tuy nhiên, vì nội dung của s2không bị thay đổi theo bất kỳ cách nào nên nó rất có thể là một chuỗi ký tự .


bạn có thể muốn thêm giải thích bổ sung về strcat: mảng đích phải đủ dài để nhận các ký tự từ s2cộng với dấu chấm hết rỗng sau khi các ký tự đã có ở đó.
chqrlie

39

Việc nối theo chuỗi ký tự được thực hiện bởi bộ tiền xử lý tại thời điểm biên dịch. Không có cách nào để nối này nhận thức được giá trị của test, giá trị này không được biết cho đến khi chương trình thực sự thực thi. Do đó, các ký tự chuỗi này không thể được nối với nhau.

Bởi vì trường hợp chung là bạn sẽ không có cấu trúc như thế này cho các giá trị đã biết tại thời điểm biên dịch, tiêu chuẩn C được thiết kế để hạn chế tính năng tự động ghép nối trong trường hợp cơ bản nhất: khi các ký tự nằm ngay bên cạnh nhau .

Nhưng ngay cả khi nó không từ hạn chế này theo cách đó, hoặc nếu hạn chế được xây dựng khác, ví dụ của bạn vẫn sẽ không thể nhận ra nếu không thực hiện nối ghép thành một quá trình thời gian chạy. Và, để làm được điều đó, chúng tôi có các chức năng thư viện như strcat.


3
Tôi chỉ đọc các giả định. Mặc dù những gì bạn nói có khá nhiều giá trị nhưng bạn không thể cung cấp nguồn cho nó vì không có nguồn nào. Nguồn duy nhất liên quan đến C là tài liệu tiêu chuẩn (mặc dù trong nhiều trường hợp là khó hiểu) không nêu lý do tại sao một số thứ lại như vậy mà chỉ nói rằng chúng phải theo cách cụ thể đó. Vì vậy, nghi ngờ rằng câu trả lời của Vlad từ câu trả lời của Moscow là không phù hợp. Vì OP có thể được chia nhỏ thành "Tại sao lại như vậy?" -Nơi câu trả lời đúng có nguồn gốc duy nhất là "Bởi vì nó là C, và đó là cách C được xác định" đó là câu trả lời đúng theo nghĩa đen duy nhất.
dhein

1
Điều này (thừa nhận) thiếu giải thích. Nhưng ở đây một lần nữa được cho là câu trả lời của Vlad phục vụ nhiều hơn nữa như một lời giải thích cho vấn đề cốt lõi mà bạn làm. Một lần nữa cho biết: Mặc dù thông tin bạn cung cấp tôi có thể xác nhận là có liên quan và chính xác, tôi không đồng ý với khiếu nại của bạn. và trong khi tôi sẽ không coi bạn là người ngoài cuộc là tốt, nó từ POV của tôi nhiều hơn người ngoài cuộc thì thực tế là Vlads.
dhein

11
@Zaibis: Nguồn là tôi. Câu trả lời của Vlad hoàn toàn không phải là lời giải thích; nó chỉ đơn thuần là một xác nhận về tiền đề của câu hỏi. Chắc chắn không ai trong số họ là "lạc đề" (bạn có thể muốn tra cứu thuật ngữ đó có nghĩa là gì). Nhưng bạn có quyền cho ý kiến ​​của bạn.
Lightness Races in Orbit,

Thậm chí sau khi đọc các bình luận trên, tôi vẫn tự hỏi ai đã phản đối câu trả lời này ᶘ ᵒᴥᵒᶅ Tôi tin rằng đây là một câu trả lời hoàn hảo trừ khi OP yêu cầu làm rõ thêm về câu trả lời này.
Mohit Jain

2
Tôi không thể phân biệt được tại sao câu trả lời này được bạn chấp nhận và câu trả lời của @ VladfromMoscow thì không, khi cả hai đều nói giống nhau, và khi câu trả lời của anh ấy được hỗ trợ bởi một trích dẫn còn của bạn thì không.
Marquis of Lorne,

30

Vì C không có stringloại. Các ký tự chuỗi được biên dịch thành charmảng, được tham chiếu bởi một char*con trỏ.

C cho phép các ký tự liền kề được kết hợp tại thời điểm biên dịch , như trong ví dụ đầu tiên của bạn. Bản thân trình biên dịch C có một số kiến ​​thức về chuỗi. Nhưng thông tin này không có trong thời gian chạy, và do đó việc nối không thể xảy ra.

Trong quá trình biên dịch, ví dụ đầu tiên của bạn được "dịch" thành:

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

Lưu ý cách hai chuỗi được trình biên dịch kết hợp thành một mảng tĩnh trước khi chương trình thực thi.

Tuy nhiên, ví dụ thứ hai của bạn được "dịch" thành một cái gì đó như thế này:

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

Cần phải rõ ràng tại sao điều này không biên dịch. Toán tử bậc ba ?được đánh giá trong thời gian chạy, không phải thời gian biên dịch, khi các "chuỗi" không còn tồn tại như vậy nữa, mà chỉ là charcác mảng đơn giản , được tham chiếu bởi char*con trỏ. Không giống như các ký tự chuỗi liền kề , các con trỏ ký tự liền kề chỉ đơn giản là một lỗi cú pháp.


2
Câu trả lời xuất sắc, có thể là tốt nhất ở đây. "Cần phải rõ ràng tại sao điều này không biên dịch." Bạn có thể xem xét mở rộng điều đó với "bởi vì toán tử bậc ba là một toán tử được đánh giá có điều kiện tại thời điểm chạy không phải thời gian biên dịch ".
cat

Không nên static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};static const char *char_ptr_1 = "HiBye";và tương tự cho phần còn lại của con trỏ?
Spikatrix

@CoolGuy Khi bạn viết static const char *char_ptr_1 = "HiBye";trình biên dịch sẽ dịch dòng thành static const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};, vì vậy không, nó không nên được viết "giống như một chuỗi". Khi trả lời cho biết, chuỗi được biên dịch để một loạt các ký tự, và nếu bạn đã gán một mảng các ký tự trong nó nhất dạng "thô", bạn sẽ sử dụng dấu phẩy tách ra danh sách các ký tự, giống nhưstatic const char *char_ptr_1 = {'H', 'i', 'B', 'y', 'e', '\0'};
Ankush

3
@Ankush Có. Nhưng mặc dù static const char str[] = {'t', 'e', 's', 't', '\0'};là giống như static const char str[] = "test";, static const char* ptr = "test";không giống như static const char* ptr = {'t', 'e', 's', 't', '\0'};. Cái trước hợp lệ và sẽ biên dịch nhưng cái sau không hợp lệ và không làm những gì bạn mong đợi.
Spikatrix

Tôi đã bổ sung đoạn cuối cùng và sửa các ví dụ mã, cảm ơn!
Không ký

12

Nếu bạn thực sự muốn có cả hai nhánh tạo ra các hằng số chuỗi thời gian biên dịch để được chọn trong thời gian chạy, bạn sẽ cần một macro.

#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

lý do cho điều đó là gì?

Mã của bạn sử dụng toán tử bậc ba chọn có điều kiện giữa hai ký tự chuỗi. Bất kể điều kiện đã biết hay chưa biết, điều này không thể được đánh giá tại thời điểm biên dịch, vì vậy nó không thể biên dịch. Ngay cả câu lệnh này printf("Hi" (1 ? "Bye" : "Goodbye"));cũng không được biên dịch. Lý do được giải thích sâu trong các câu trả lời ở trên. Một khả năng làm như vậy một tuyên bố sử dụng nhà điều hành ternary hợp lệ để biên dịch , cũng sẽ bao gồm một thẻ định dạng và kết quả của báo cáo kết quả điều hành ternary định dạng như đối số bổ sung để printf. Ngay cả khi đó, printf()bản in sẽ tạo ấn tượng về việc "đã nối" các chuỗi đó chỉ tại và sớm nhất là thời gian chạy .

#include <stdio.h>

int main() {
    int test = 0;
    printf("Hi %s\n", (test ? "Bye" : "Goodbye")); //specify format and print as result
}

3
SO không phải là trang Hướng dẫn. Bạn nên đưa ra câu trả lời cho OP chứ không phải hướng dẫn.
Michi

1
Điều này không trả lời câu hỏi của OP. Nó có thể là một nỗ lực để giải quyết vấn đề cơ bản của OP, nhưng chúng tôi không thực sự biết đó là gì.
Keith Thompson

1
printfkhông yêu cầu định dạng; nếu chỉ nối được thực hiện tại thời điểm biên dịch (mà không phải vậy), việc sử dụng printf của OP sẽ hợp lệ.
David Conrad

Cảm ơn nhận xét của bạn, @David Conrad. Từ ngữ cẩu thả của tôi thực sự sẽ làm xuất hiện như thể tuyên bố printf()sẽ yêu cầu thẻ định dạng, điều này hoàn toàn không đúng. Đã sửa!
user3078414

Đó là một cách diễn đạt tốt hơn. +1 Cảm ơn.
David Conrad

7

Trong printf("Hi" "Bye");bạn có hai mảng char liên tiếp mà trình biên dịch có thể tạo thành một mảng duy nhất.

Trong printf("Hi" (test ? "Bye" : "Goodbye"));bạn có một mảng được theo sau bởi một con trỏ tới char (một mảng được chuyển đổi thành một con trỏ đến phần tử đầu tiên của nó). Trình biên dịch không thể hợp nhất một mảng và một con trỏ.


0

Để trả lời câu hỏi - tôi sẽ đi đến định nghĩa của printf. Hàm printf mong đợi const char * là đối số. Bất kỳ ký tự chuỗi nào chẳng hạn như "Hi" là một const char *; tuy nhiên, một biểu thức như (test)? "str1" : "str2"KHÔNG phải là const char * vì kết quả của biểu thức đó chỉ được tìm thấy tại thời điểm chạy và do đó không xác định được tại thời điểm biên dịch, một thực tế khiến trình biên dịch phàn nàn. Mặt khác - điều này hoạt động hoàn toàn tốtprintf("hi %s", test? "yes":"no")


* Tuy nhiên, một biểu thức chẳng hạn như (test)? "str1" : "str2"KHÔNG phải là const char*... Tất nhiên là như vậy! Nó không phải là một biểu thức hằng, mà kiểu của nó const char *. Nó sẽ là hoàn toàn tốt để viết printf(test ? "hi " "yes" : "hi " "no"). Vấn đề của OP không liên quan gì printf, "Hi" (test ? "Bye" : "Goodbye")là lỗi cú pháp bất kể ngữ cảnh biểu thức là gì.
chqrlie

Đã đồng ý. Tôi đã nhầm lẫn đầu ra của một biểu thức với chính biểu thức đó
Stats_Lover

-4

Điều này không biên dịch vì danh sách tham số cho hàm printf là

(const char *format, ...)

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

không phù hợp với danh sách tham số.

gcc cố gắng hiểu nó bằng cách tưởng tượng

(test ? "Bye" : "Goodbye")

là một danh sách tham số và phàn nàn rằng "Hi" không phải là một hàm.


6
Chào mừng bạn đến với Stack Overflow. Bạn nói đúng rằng nó không khớp với printf()danh sách đối số, nhưng đó là vì biểu thức không hợp lệ ở bất kỳ đâu - không chỉ trong printf()danh sách đối số. Nói cách khác, bạn đã chọn ra một lý do quá chuyên biệt cho vấn đề; vấn đề chung "Hi" (là không hợp lệ trong C, hãy để một mình trong một cuộc gọi đến printf(). Tôi khuyên bạn nên xóa câu trả lời này trước khi nó bị bỏ phiếu.
Jonathan Leffler

Đó không phải là cách C hoạt động. Điều này không được phân tích cú pháp như cố gắng gọi một chuỗi theo nghĩa đen như PHP.
con mèo
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.