2013-05-17 67 views
7

Tôi thực sự cần giúp đỡ về điều này. Nó đã lung lay nền tảng của tôi trong C.Long và câu trả lời chi tiết sẽ được rất nhiều appreci.Tôi đã chia câu hỏi của tôi thành hai phần.Tại sao printf ("% s", (char []) {'H', 'i', ' 0'}) hoạt động như printf ("% s", "Hi"), nhưng printf ("% s" , (char *) {'H', 'i', ' 0'}); thất bại?

A: Tại sao printf("%s",(char[]){'H','i','\0'}); công việc và in Hi giống như truyền thống printf("%s","Hi"); làm chúng ta có thể sử dụng (char[]){'H','i','\0'} như một sự thay thế cho "Hi" bất cứ nơi nào trong mã C của chúng tôi Họ có ý nghĩa giống Ý tôi là, khi chúng tôi viết "Hi"?? trong C, nó thường có nghĩa là Hi được lưu trữ ở đâu đó trong bộ nhớ và một con trỏ đến nó được thông qua. Có thể nói tương tự như có vẻ xấu xí (char[]){'H','i','\0'} .Có phải họ chính xác là giống nhau không?

B: Khi printf("%s",(char[]){'H','i','\0'}) làm việc thành công, giống như printf("%s","Hi"), tại sao sau đó printf("%s",(char*){'A','B','\0'} thất bại thời gian lớn và seg-lỗi nếu tôi chạy nó bất chấp những lời cảnh báo? Nó chỉ làm tôi kinh ngạc, bởi vì, trong C, không phải là char[] phải phân chia thành char*, giống như khi chúng ta truyền nó trong các đối số hàm, tại sao nó không làm như vậy ở đây và char* cho thất bại? làm đối số cho hàm giống như char demo*? Tại sao kết quả không giống nhau ở đây?

Xin hãy giúp tôi về điều này.Tôi cảm thấy như tôi chưa hiểu những điều cơ bản của C. Tôi rất thất vọng. Cảm ơn bạn !!

+12

Mảng không phải là con trỏ và con trỏ không phải là mảng. –

+0

Nhưng một tham số mảng cho một hàm thoái hóa thành một con trỏ. –

+0

@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

Trả lời

7

Về đoạn mã số 2:

Mã hoạt động vì tính năng mới trong C99, được gọi là chữ kép. Bạn có thể đọc về chúng ở một số địa điểm, bao gồm GCC's documentation, Mike Ash's article và một chút tìm kiếm trên google.

Về cơ bản, trình biên dịch tạo một mảng tạm thời trên ngăn xếp và điền vào nó với 3 byte - 0x48, 0x690x00. Mảng tạm thời đó đã được tạo ra, sau đó được phân rã thành một con trỏ và được chuyển tới hàm printf. Một điều rất quan trọng cần lưu ý về các ký tự hợp chất là chúng không phải là const theo mặc định, giống như hầu hết các chuỗi C.

Về đoạn # 3:

Bạn đang thực sự không tạo ra một mảng ở tất cả - bạn đang đúc phần tử đầu tiên trong intializer vô hướng, trong đó, trong trường hợp này là H, hoặc 0x48 vào một con trỏ. Bạn có thể thấy rằng bằng cách thay đổi %s trong bản Tuyên Bố printf của bạn thành một %p, mang đến cho sản lượng này cho tôi:

 
0x48 

Như vậy, bạn phải rất cẩn thận với những gì bạn làm với literals hợp chất - họ là một công cụ mạnh mẽ, nhưng thật dễ dàng để tự bắn mình vào chân với chúng.

+0

+1 cho câu trả lời.Xin vui lòng không nhớ rollback.I hầu như không thể nhớ lại những gì tôi dự định để hỏi như câu hỏi là trong lời nói của tôi và có một số sắc thái. – Thokchom

+0

Hãy nhìn vào những gì tôi yêu cầu ** Keith Thompson ** dưới câu trả lời của anh ấy. Tôi sẽ rất vui nếu bạn có thể làm rõ điều đó. – Thokchom

3

(Ok ... ai đó đã làm lại câu hỏi hoàn chỉnh. Làm lại câu trả lời.)

Mảng # 3 chứa các byte hex. (Chúng tôi không biết về điều đó một lần thứ 4):

48 49 00 xx

Khi nó chuyển nội dung của mảng đó, chỉ trong trường hợp thứ 2, phải mất những byte như địa chỉ của chuỗi in. Nó phụ thuộc vào cách 4 byte đó chuyển thành một con trỏ trong phần cứng CPU thực tế của bạn nhưng cho phép nói rằng nó nói "414200FF" là địa chỉ (vì chúng ta sẽ đoán byte thứ 4 là 0xFF. Chúng ta đang làm tất cả điều này.) cũng giả định một con trỏ dài 4 byte và một thứ tự endian và các công cụ như thế. Nó không quan trọng để trả lời nhưng những người khác được tự do để giải thích.

Lưu ý: Một trong các câu trả lời khác dường như nghĩ rằng nó lấy 0x48 và mở rộng nó thành một (int) 0x00000048 và gọi đó là một con trỏ. Có thể là. Nhưng nếu GCC đã làm điều đó, và @KiethThompson đã không nói rằng ông đã kiểm tra mã được tạo ra, nó không có nghĩa là một số trình biên dịch C khác sẽ làm điều tương tự. Kết quả cũng giống nhau.

Điều đó được chuyển đến hàm printf() và nó cố gắng đi đến địa chỉ đó để lấy một số ký tự cần in. (Lỗi Seg xảy ra vì địa chỉ đó có thể không có trên máy và không được gán cho quá trình đọc của bạn.)

Trong trường hợp # 2 nó biết một mảng và không phải con trỏ để nó chuyển địa chỉ của bộ nhớ nơi các byte được lưu trữ và printf() có thể làm điều đó.

Xem các câu trả lời khác cho ngôn ngữ chính thức hơn.

Một điều cần suy nghĩ là ít nhất một trình biên dịch C có thể không biết cuộc gọi đến printf từ một cuộc gọi đến bất kỳ chức năng nào khác.Vì vậy, phải mất "format string" và lưu trữ một con trỏ cho cuộc gọi (xảy ra với một chuỗi) và sau đó lấy tham số thứ 2 và lưu trữ bất kỳ giá trị nào theo tuyên bố của hàm, cho dù là int hoặc char hoặc con trỏ cho cuộc gọi. Hàm này sau đó kéo chúng ra khỏi bất cứ nơi nào người gọi đặt chúng theo cùng khai báo đó. Việc khai báo các tham số thứ 2 và lớn hơn phải là một cái gì đó thực sự chung chung để có thể chấp nhận con trỏ, int, double và tất cả các kiểu khác nhau có thể có ở đó. (Những gì tôi đang nói là trình biên dịch có lẽ không nhìn vào chuỗi định dạng khi quyết định phải làm gì với các thông số thứ 2 và sau.)

Nó có thể là thú vị để xem những gì sẽ xảy ra cho:

printf("%s",{'H','i','\0'}); 
printf("%s",(char *)(char[]){'H','i','\0'}); // This works according to @DanielFischer 

Dự đoán?

+0

'printf ("% s ", (char *) (char []) {'H', 'i', '\ 0'});' sẽ hoạt động. Bạn đang đúc một 'char []' (ký tự hợp chất) thành 'char *' [chuyển đổi nào sẽ tự động được thực hiện anyway], hoàn toàn hợp lệ, không thành vấn đề. –

+0

@DanielFischer Tôi cần một số giải thích rõ ràng hơn mà tôi đã không đề cập rõ ràng trong câu hỏi của mình. Tôi đã đề cập đến những nhận xét đó dưới câu trả lời của Keith. Bạn có thể dành một phút để đăng câu trả lời của riêng mình cho những người đó không? – Thokchom

+0

@DanielFischer Để đặt nó rõ ràng ** 1) ** Vì '% s' mong đợi một đối số' char * ', có nghĩa là' (char []) {'H', 'i', '\ 0'} ' dịch để loại 'char *' cuối cùng? ** 2) ** Là '(char []) {'H', 'i', '\ 0'}' ** chính xác ** giống với '" Hi "', trong mọi khía cạnh? Chúng ta có thể sử dụng nó không bất cứ khi nào chúng ta muốn sử dụng chuỗi '" Hi "' như đối số cho các hàm thư viện như strlen() hoặc trong khi gán cho con trỏ? Nó có được bảo đảm là kiểu 'char *' do dịch/phân tách từ kiểu 'char [] hay không 'to' char * '? – Thokchom

2

Trong mỗi trường hợp, trình biên dịch tạo đối tượng khởi tạo kiểu char [3]. Trong trường hợp đầu tiên, nó xử lý đối tượng như là một mảng, vì vậy nó chuyển một con trỏ tới phần tử đầu tiên của nó đến hàm. Trong trường hợp thứ hai, nó xử lý đối tượng như một con trỏ, vì vậy nó vượt qua giá trị của đối tượng. printf đang mong đợi một con trỏ, và giá trị của đối tượng không hợp lệ khi được coi là một con trỏ, do đó chương trình sẽ bị treo khi chạy.

+2

"Nó xử lý đối tượng như một con trỏ" nghĩa là gì? Nó là nội dung byte chính xác của mảng như Lee Meador nghi ngờ? –

+0

Con trỏ được truyền theo giá trị. Mảng được truyền qua một con trỏ tới phần tử đầu tiên. Các diễn viên nói với trình biên dịch để xử lý các đối tượng như một con trỏ, vì vậy nó vượt qua nó bằng giá trị, bởi vì con trỏ được thông qua bởi giá trị. –

+0

@WilliamPursell không có đối tượng trong C, nhưng trong C++. Từ 'đối tượng' chính xác là gì? – stackoverflowery

-1

Phiên bản thứ ba không nên biên dịch. 'H' không phải là trình khởi tạo hợp lệ cho loại con trỏ. GCC cung cấp cho bạn một cảnh báo nhưng không phải là lỗi theo mặc định.

+0

Nó biên dịch, bởi vì nó là trong thực tế, một chương trình C hợp lệ. Theo tiêu chuẩn, bộ khởi tạo vô hướng có thể có các phần tử dư thừa, nên bỏ qua. –

+1

''H'' vẫn không phải là bộ khởi tạo hợp lệ cho' char * '. Không có chuyển đổi ngầm từ 'int' (kiểu' 'H'') thành 'char *', trừ trường hợp đặc biệt của hằng số con trỏ null. –

+0

Không, @ RichardJ.RossIII, "Trình khởi tạo cho một vô hướng phải là một biểu thức duy nhất, được tùy ý đính kèm trong niềng răng." Những người khởi tạo dư thừa gọi hành vi không xác định. Trình biên dịch không cần chấp nhận nó. –

8

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''\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 ý:

  1. 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.

  2. 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ể.

+0

Tôi nhận được ** cảnh báo ** chính xác như bạn đã đề cập trong câu trả lời của mình !! – Thokchom

+0

Tôi thực sự không may khi tôi trả lời. Tôi định hỏi câu hỏi này: Tại sao '(char []) {'H', 'i', '\ 0'}' làm việc? '% S' kỳ vọng một đối số gõ 'char *' .Tôi sẽ kết thúc '(char []) {'H', 'i', '\ 0'}' phân tách thành 'char *' cuối cùng? Và cuối cùng, là '(char []) { 'H', 'i', '\ 0'} 'thay thế ** chính xác ** cho' "Xin chào" '? – Thokchom

+0

Tôi phải nhấn mạnh những gì tôi định yêu cầu trong phần thứ hai của bình luận của tôi ở trên.Does '' (char []) {'H', 'i', '\ 0'} '' có nghĩa là chuỗi '" Hi "' trong tất cả các bối cảnh trong một mã C? Giống như strlen(), sizeof, strcpy()? – Thokchom

Các vấn đề liên quan