Ví dụ thứ ba của bạn:
printf("%s",(char *){'H','i','\0'});
là thậm chí không hợp pháp (nói đúng đó là một vi phạm chế), và bạn nên đã nhận được ít nhất một cảnh báo khi biên dịch nó.Khi tôi biên dịch nó với gcc với các tùy chọn mặc định, tôi có 6 cảnh báo:
c.c:3:5: warning: initialization makes pointer from integer without a cast [enabled by default]
c.c:3:5: warning: (near initialization for ‘(anonymous)’) [enabled by default]
c.c:3:5: warning: excess elements in scalar initializer [enabled by default]
c.c:3:5: warning: (near initialization for ‘(anonymous)’) [enabled by default]
c.c:3:5: warning: excess elements in scalar initializer [enabled by default]
c.c:3:5: warning: (near initialization for ‘(anonymous)’) [enabled by default]
Đối số thứ hai để printf
là một hợp chất đen. Đó là hợp pháp (nhưng lẻ) để có một hợp chất theo kiểu của char*
, nhưng trong trường hợp này, initializer-list phần của hợp chất theo nghĩa đen là không hợp lệ.
Sau khi in những lời cảnh báo, những gì gcc có vẻ là làm được (a) chuyển đổi biểu thức 'H'
, mà là loại int
, để char*
, năng suất một giá trị con trỏ rác, và (b) bỏ qua phần còn lại của các yếu tố khởi tạo , 'i'
và '\0'
. Kết quả là giá trị con trỏ char*
trỏ đến địa chỉ (có thể là ảo) 0x48
- giả định một bộ ký tự dựa trên ASCII.
Bỏ qua bộ khởi tạo dư thừa hợp lệ (nhưng đáng để cảnh báo), nhưng không có chuyển đổi ẩn từ int
thành char*
(ngoài trường hợp đặc biệt của hằng số con trỏ null không áp dụng tại đây). gcc đã thực hiện công việc của mình bằng cách đưa ra một cảnh báo, nhưng nó có thể (và IMHO nên) đã từ chối nó với một thông báo lỗi nghiêm trọng. Nó sẽ làm như vậy với tùy chọn -pedantic-errors
.
Nếu trình biên dịch của bạn cảnh báo bạn về những dòng này, bạn nên bao gồm những cảnh báo đó trong câu hỏi của bạn. Nếu nó không, hoặc crank lên mức cảnh báo hoặc có được một trình biên dịch tốt hơn. Đi
vào chi tiết hơn về những gì xảy ra trong mỗi một trong ba trường hợp:
printf("%s","Hi");
Một C chuỗi chữ như "%s"
hoặc "Hi"
tạo ra một mảng tĩnh được phân bổ mang tính chất của char
. (Đối tượng này không phải là const
, nhưng cố gắng sửa đổi nó có hành vi không xác định; đây không phải là lý tưởng, nhưng có lý do lịch sử cho nó.) Một ký tự null là '\0'
được thêm vào để làm cho nó trở thành một chuỗi hợp lệ.
Một biểu hiện của kiểu mảng, trong hầu hết các tình huống (các trường hợp ngoại lệ là khi đó là toán hạng của unary sizeof
hoặc &
điều hành, hoặc khi đó là một chuỗi chữ trong một initializer sử dụng để khởi tạo một đối tượng mảng) được ngầm chuyển đổi sang ("decays to") một con trỏ tới phần tử đầu tiên của mảng. Vì vậy, hai đối số được chuyển đến printf
là loại char*
; printf
sử dụng các con trỏ đó để đi qua các mảng tương ứng.
printf("%s",(char[]){'H','i','\0'});
này sử dụng một tính năng được thêm vào ngôn ngữ bằng cách C99 (phiên bản 1999 của tiêu chuẩn ISO C), được gọi là một hợp chất đen. Nó tương tự như một chuỗi chữ, trong đó nó tạo ra một đối tượng ẩn danh và đề cập đến giá trị của đối tượng đó. Hợp chất theo nghĩa đen có dạng:
(type-name) { initializer-list }
và đối tượng có loại được chỉ định và được khởi tạo với giá trị được đưa ra bởi danh sách khởi tạo.
Trên đây là gần tương đương với:
char anon[] = {'H', 'i', '\0'};
printf("%s", anon);
Một lần nữa, đối số thứ hai để printf
đề cập đến một đối tượng mảng, và nó "phân rã" tới con trỏ đến phần tử đầu tiên của mảng; printf
sử dụng con trỏ đó để đi qua mảng.
Cuối cùng, điều này:
printf("%s",(char*){'A','B','\0'});
như bạn nói, thất bại thời gian lớn. Loại chữ thường là một mảng hoặc cấu trúc (hoặc công đoàn); nó thực sự đã không xảy ra với tôi rằng nó có thể là một loại vô hướng như một con trỏ. Trên đây là gần tương đương với:
char *anon = {'A', 'B', '\0'};
printf("%s", anon);
Rõ ràng anon
là loại char*
, đó là những gì printf
hy vọng cho một định dạng "%s"
. Nhưng giá trị ban đầu là gì?
Tiêu chuẩn yêu cầu trình khởi tạo cho đối tượng vô hướng là một biểu thức duy nhất, được tùy ý đính kèm trong dấu ngoặc nhọn. Nhưng vì lý do nào đó, yêu cầu đó nằm trong "Ngữ nghĩa", vì vậy vi phạm nó không phải là một sự vi phạm ràng buộc; nó chỉ là hành vi không xác định. Điều đó có nghĩa là trình biên dịch có thể làm bất cứ điều gì nó thích, và có thể hoặc không thể đưa ra một chẩn đoán. Các tác giả của gcc dường như đã quyết định đưa ra một cảnh báo và bỏ qua tất cả trừ bộ khởi tạo đầu tiên trong danh sách.
Sau đó, nó trở nên tương đương với:
char *anon = 'A';
printf("%s", anon);
Hằng 'A'
là loại int
(vì lý do lịch sử, đó là int
hơn char
, nhưng lập luận tương tự sẽ áp dụng một trong hai cách). Không có chuyển đổi tiềm ẩn từ int
thành char*
và trên thực tế trình khởi tạo ở trên là một vi phạm ràng buộc. Điều đó có nghĩa là trình biên dịch phải phát hành chẩn đoán (gcc) và có thể từ chối chương trình (gcc không trừ khi bạn sử dụng -pedantic-errors
). Một khi chẩn đoán được ban hành, trình biên dịch có thể làm bất cứ điều gì nó thích; hành vi là không xác định (có một số bất đồng ngôn ngữ-luật sư vào thời điểm đó, nhưng nó không thực sự quan trọng). gcc chọn chuyển đổi giá trị A
từ int
thành char*
(có thể vì lý do lịch sử, quay trở lại khi C thậm chí ít được nhập mạnh hơn ngày hôm nay), dẫn đến con trỏ rác. 0x00000041
hoặc 0x0000000000000041`.
Con trỏ rác đó sau đó được chuyển đến printf
, cố gắng sử dụng nó để truy cập một chuỗi tại vị trí đó trong bộ nhớ. Nảy sinh vui nhộn.
Có hai điều quan trọng cần lưu ý:
Nếu in biên dịch của bạn cảnh báo, chú ý gần gũi với họ. gcc đặc biệt đưa ra cảnh báo cho nhiều thứ mà IMHO phải là lỗi nghiêm trọng. Không bao giờ bỏ qua cảnh báo trừ khi bạn hiểu ý nghĩa của cảnh báo, đủ kỹ lưỡng để hiểu biết của bạn về việc ghi đè tác giả của trình biên dịch.
Mảng và con trỏ là những thứ rất khác nhau. Một số quy tắc của ngôn ngữ C dường như âm mưu để làm cho nó trông giống như họ đang giống nhau. Bạn có thể tạm thời tránh xa với giả định rằng mảng không có gì hơn con trỏ trong ngụy trang, nhưng giả định đó cuối cùng sẽ trở lại để cắn bạn. Đọc phần 6 của số comp.lang.c FAQ; nó giải thích mối quan hệ giữa các mảng và con trỏ tốt hơn tôi có thể.
Mảng không phải là con trỏ và con trỏ không phải là mảng. –
Nhưng một tham số mảng cho một hàm thoái hóa thành một con trỏ. –
@WilliamPursell Tôi đã lưu ý rằng trong máy tính xách tay của tôi dài trở lại và đọc nhiều lần trên SO. – Thokchom