Lưu trữ ký tự EOF (Kết thúc tệp) theo kiểu char


11

Tôi đã đọc trong Dennis Ritchie Cuốn sách Ngôn ngữ lập trình Cint phải được sử dụng cho một biến để giữ EOF - để làm cho nó đủ lớn để nó có thể giữ giá trị EOF - không char. Nhưng đoạn mã sau hoạt động tốt:

#include<stdio.h> 

main()  { 
  char c; 
  c=getchar(); 
  while(c!=EOF)  { 
    putchar(c); 
    c=getchar(); 
  } 
} 

Khi không còn đầu vào, getchartrả về EOF. Và trong chương trình trên, biến c, với kiểu char, có thể giữ nó thành công.

Tại sao điều này làm việc? Theo giải thích trong cuốn sách nói trên, mã không nên hoạt động.



5
Mã này có khả năng thất bại nếu bạn đọc một ký tự có giá trị 0xff. Lưu trữ kết quả getchar()trong một intgiải quyết vấn đề đó. Câu hỏi của bạn về cơ bản giống như câu hỏi 12.1 trong Câu hỏi thường gặp comp.lang.c , đây là một tài nguyên tuyệt vời. (Ngoài ra, main()nên int main(void), và sẽ không đau khi thêm return 0;trước khi kết thúc }.)
Keith Thompson

1
@delnan: Bài viết được liên kết không hoàn toàn đúng về cách Unix đối xử với control-D. Nó không đóng luồng đầu vào; nó chỉ khiến cho bất kỳ fread () nào đang chặn trên bàn điều khiển quay trở lại ngay lập tức với bất kỳ dữ liệu nào chưa đọc. Nhiều chương trình diễn giải trả về 0 byte từ fread () như biểu thị EOF, nhưng thực tế tệp sẽ vẫn mở và có thể cung cấp thêm đầu vào.
supercat

Câu trả lời:


11

Mã của bạn dường như hoạt động, bởi vì các chuyển đổi loại ẩn vô tình xảy ra để làm điều đúng.

getchar()trả về intmột giá trị phù hợp với phạm vi unsigned charhoặc là EOF(phải âm, thường là -1). Lưu ý rằng EOFbản thân nó không phải là một ký tự, mà là một tín hiệu cho thấy không còn ký tự nào nữa.

Khi lưu trữ kết quả từ getchar()trong c, có hai khả năng. Loại charcó thể đại diện cho giá trị, trong trường hợp đó là giá trị của c. Hoặc loại char không thể đại diện cho giá trị. Trong trường hợp đó, nó không được xác định những gì sẽ xảy ra. Bộ xử lý Intel chỉ loại bỏ các bit cao không phù hợp với loại mới (giảm hiệu quả modulo 256 cho char), nhưng bạn không nên dựa vào điều đó.

Bước tiếp theo là so sánh cvới EOF. Như EOFlà một int, ccũng sẽ được chuyển đổi thành một int, bảo toàn giá trị được lưu trữ trong c. Nếu ccó thể lưu trữ các giá trị EOF, sau đó so sánh sẽ thành công, nhưng nếu ccó thể không lưu trữ giá trị, sau đó so sánh sẽ thất bại, bởi vì đã có một sự mất mát không thu hồi thông tin trong khi chuyển đổi EOFđể loại char.

Có vẻ như trình biên dịch của bạn đã chọn làm cho charloại đã ký và giá trị EOFđủ nhỏ để phù hợp char. Nếu charkhông được ký (hoặc nếu bạn đã sử dụng unsigned char), thử nghiệm của bạn sẽ thất bại, vì unsigned charkhông thể giữ giá trị của EOF.


Cũng lưu ý rằng có một vấn đề thứ hai với mã của bạn. Vì EOFbản thân nó không phải là một nhân vật, nhưng bạn buộc nó thành một charloại, rất có thể một nhân vật ngoài đó bị hiểu sai là EOFvà đối với một nửa các ký tự có thể, nó sẽ không được xác định nếu chúng được xử lý chính xác.


Việc ép buộc charcác giá trị ngoài phạm vi CHAR_MIN.. CHAR_MAXsẽ được yêu cầu để mang lại giá trị Xác định thực hiện, mang lại một mẫu bit mà việc triển khai xác định là biểu diễn bẫy hoặc tăng tín hiệu do xác định thực hiện. Trong hầu hết các trường hợp, việc triển khai sẽ phải trải qua rất nhiều công việc phụ để làm bất cứ điều gì khác ngoài việc giảm hai phần bù. Nếu mọi người trong Ủy ban Tiêu chuẩn đăng ký ý tưởng rằng các trình biên dịch nên được khuyến khích thực hiện các hành vi phù hợp với hầu hết các trình biên dịch khác trong trường hợp không có lý do để làm khác ...
supercat

... Tôi sẽ coi sự ép buộc đó là đáng tin cậy (không nói rằng mã không nên ghi lại ý định của nó, nhưng điều đó (signed char)xnên được coi là rõ ràng hơn và an toàn như ((unsigned char)x ^ CHAR_MAX+1))-(CHAR_MAX+1).) Vì vậy, tôi không thấy bất kỳ khả năng nào của trình biên dịch thực hiện bất kỳ hành vi nào khác tuân thủ tiêu chuẩn ngày nay; một mối nguy hiểm là Tiêu chuẩn có thể được thay đổi để phá vỡ hành vi vì lợi ích được cho là "tối ưu hóa".
supercat

@supercat: Tiêu chuẩn được viết sao cho không có trình biên dịch nào phải tạo mã có hành vi không được bộ xử lý hỗ trợ một cách tự nhiên. Hầu hết các hành vi không xác định là có bởi vì (tại thời điểm viết tiêu chuẩn) không phải tất cả các bộ xử lý đều hành xử nhất quán. Với các trình biên dịch ngày càng hoàn thiện hơn, các nhà văn trình biên dịch đã bắt đầu lợi dụng các hành vi không xác định để thực hiện các tối ưu hóa mạnh mẽ hơn.
Bart van Ingen Schenau 7/8/2015

Về mặt lịch sử, ý định của Tiêu chuẩn chủ yếu là như bạn mô tả, mặc dù Tiêu chuẩn mô tả một số hành vi đủ chi tiết để yêu cầu trình biên dịch cho một số nền tảng phổ biến để tạo ra nhiều mã hơn so với yêu cầu kỹ thuật lỏng lẻo hơn. Loại cưỡng chế trong int i=129; signed char c=i;là một hành vi như vậy. Một số bộ xử lý tương đối có một lệnh sẽ cbằng nhau ikhi nó nằm trong phạm vi -127 đến +127 và sẽ mang lại bất kỳ ánh xạ nhất quán nào cho các giá trị khác itrong phạm vi -128 đến +127 khác với giảm hai phần bù, hoặc. ..
supercat

... Sẽ liên tục tăng tín hiệu trong những trường hợp như vậy. Do Tiêu chuẩn yêu cầu việc triển khai mang lại ánh xạ nhất quán hoặc tăng tín hiệu nhất quán, nên các nền tảng duy nhất mà Tiêu chuẩn sẽ chừa chỗ cho thứ gì đó ngoài việc giảm hai phần bù sẽ là những thứ như DSP với phần cứng số học bão hòa. Đối với cơ sở lịch sử cho Hành vi không xác định, tôi sẽ nói rằng vấn đề không chỉ xảy ra với các nền tảng phần cứng. Ngay cả trên một nền tảng nơi tràn sẽ hoạt động theo một kiểu rất nhất quán, có thể hữu ích khi có một trình biên dịch bẫy nó ...
supercat
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.