Saya telah melihat orang yang mencoba membaca file seperti ini di banyak posting belakangan ini:
#include <stdio.h>
#include <stdlib.h>
int
main(int argc, char **argv)
{
char *path = "stdin";
FILE *fp = argc > 1 ? fopen(path=argv[1], "r") : stdin;
if( fp == NULL ) {
perror(path);
return EXIT_FAILURE;
}
while( !feof(fp) ) { /* THIS IS WRONG */
/* Read and process data from file… */
}
if( fclose(fp) != 0 ) {
perror(path);
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
Apa yang salah dengan loop ini?
feof()
untuk mengendalikan satu lingkaranJawaban:
Saya ingin memberikan perspektif abstrak tingkat tinggi.
Konkurensi dan simultan
Operasi I / O berinteraksi dengan lingkungan. Lingkungan bukan bagian dari program Anda, dan bukan di bawah kendali Anda. Lingkungan benar-benar ada "bersamaan" dengan program Anda. Seperti halnya semua hal bersamaan, pertanyaan tentang "kondisi saat ini" tidak masuk akal: Tidak ada konsep "simultanitas" di seluruh peristiwa yang terjadi bersamaan. Banyak sifat negara tidak ada secara bersamaan.
Biarkan saya membuat ini lebih tepat: Misalkan Anda ingin bertanya, "apakah Anda memiliki lebih banyak data". Anda bisa menanyakan ini pada wadah bersamaan, atau sistem I / O Anda. Tetapi jawabannya pada umumnya tidak dapat dipertanyakan, dan dengan demikian tidak berarti. Jadi bagaimana jika wadah itu mengatakan "ya" - pada saat Anda mencoba membaca, itu mungkin tidak lagi memiliki data. Demikian pula, jika jawabannya "tidak", pada saat Anda mencoba membaca, data mungkin telah tiba. Kesimpulannya adalah bahwa ada cukup adalahtidak ada properti seperti "Saya punya data", karena Anda tidak dapat bertindak secara bermakna sebagai jawaban atas kemungkinan jawaban. (Situasinya sedikit lebih baik dengan input buffered, di mana Anda mungkin mendapatkan "ya, saya punya data" yang merupakan semacam jaminan, tetapi Anda masih harus mampu menangani kasus yang berlawanan. Dan dengan output situasi tentu saja sama buruknya dengan yang saya jelaskan: Anda tidak pernah tahu apakah disk itu atau buffer jaringan sudah penuh.)
Jadi kami menyimpulkan bahwa tidak mungkin, dan pada kenyataannya tidak masuk akal , untuk menanyakan sistem I / O apakah akan dapat melakukan operasi I / O. Satu-satunya cara yang memungkinkan kita untuk berinteraksi dengannya (seperti halnya wadah bersamaan) adalah dengan mencoba operasi dan memeriksa apakah berhasil atau gagal. Pada saat di mana Anda berinteraksi dengan lingkungan, saat itu dan baru Anda dapat mengetahui apakah interaksi itu benar-benar mungkin, dan pada saat itu Anda harus berkomitmen untuk melakukan interaksi. (Ini adalah "titik sinkronisasi", jika Anda mau.)
EOF
Sekarang kita sampai ke EOF. EOF adalah respons yang Anda dapatkan dari percobaan operasi I / O. Ini berarti bahwa Anda mencoba membaca atau menulis sesuatu, tetapi ketika melakukannya Anda gagal membaca atau menulis data apa pun, dan sebaliknya akhir input atau output itu ditemukan. Ini berlaku untuk dasarnya semua I / O API, apakah itu pustaka standar C, C ++ iostreams, atau pustaka lainnya. Selama operasi I / O berhasil, Anda tidak dapat mengetahui apakah operasi selanjutnya di masa depan akan berhasil. Anda harus selalu mencoba operasi terlebih dahulu dan kemudian menanggapi keberhasilan atau kegagalan.
Contohnya
Dalam masing-masing contoh, perhatikan dengan seksama bahwa kita pertama-tama mencoba operasi I / O dan kemudian mengkonsumsi hasilnya jika itu valid. Perhatikan lebih lanjut bahwa kita selalu harus menggunakan hasil operasi I / O, meskipun hasilnya mengambil bentuk dan bentuk yang berbeda dalam setiap contoh.
C stdio, baca dari file:
Hasil yang harus kita gunakan adalah
n
, jumlah elemen yang dibaca (yang mungkin hanya nol).C stdio,
scanf
:Hasil yang harus kita gunakan adalah nilai balik
scanf
, jumlah elemen yang dikonversi.C ++, iostreams diekstraksi dengan format:
Hasil yang harus kita gunakan adalah
std::cin
dirinya sendiri, yang dapat dievaluasi dalam konteks boolean dan memberi tahu kita apakah aliran masih dalamgood()
keadaan.C ++, iostreams getline:
Hasil yang harus kita gunakan lagi
std::cin
, sama seperti sebelumnya.POSIX,
write(2)
untuk menyiram buffer:Hasil yang kami gunakan di sini adalah
k
, jumlah byte yang ditulis. Intinya di sini adalah bahwa kita hanya bisa tahu berapa byte yang ditulis setelah operasi penulisan.POSIX
getline()
Hasil yang harus kita gunakan adalah
nbytes
, jumlah byte hingga dan termasuk baris baru (atau EOF jika file tidak diakhiri dengan baris baru).Perhatikan bahwa fungsi secara eksplisit mengembalikan
-1
(dan bukan EOF!) Ketika kesalahan terjadi atau mencapai EOF.Anda mungkin memperhatikan bahwa kami sangat jarang mengeja kata "EOF" yang sebenarnya. Kami biasanya mendeteksi kondisi kesalahan dengan cara lain yang lebih menarik bagi kami (mis. Kegagalan untuk melakukan I / O sebanyak yang kami inginkan). Dalam setiap contoh ada beberapa fitur API yang dapat memberi tahu kami secara eksplisit bahwa keadaan EOF telah dijumpai, tetapi ini sebenarnya bukan sepotong informasi yang sangat berguna. Ini jauh lebih detail daripada yang sering kita pedulikan. Yang penting adalah apakah I / O berhasil, lebih-daripada bagaimana gagal.
Contoh terakhir yang benar-benar menanyakan keadaan EOF: Misalkan Anda memiliki string dan ingin menguji bahwa itu mewakili bilangan bulat secara keseluruhan, tanpa bit tambahan di akhir kecuali spasi. Menggunakan C ++ iostreams, seperti ini:
Kami menggunakan dua hasil di sini. Yang pertama adalah
iss
, objek stream itu sendiri, untuk memeriksa apakah ekstraksi yang diformatvalue
berhasil. Tetapi kemudian, setelah juga mengkonsumsi spasi putih, kami melakukan operasi I / O / lainnyaiss.get()
,, dan mengharapkannya gagal sebagai EOF, yang merupakan kasus jika seluruh string telah dikonsumsi oleh ekstraksi yang diformat.Di pustaka standar C Anda dapat mencapai sesuatu yang mirip dengan
strto*l
fungsi dengan memeriksa bahwa pointer akhir telah mencapai akhir string input.Jawabannya
while(!feof)
salah karena tes untuk sesuatu yang tidak relevan dan gagal untuk menguji sesuatu yang perlu Anda ketahui. Hasilnya adalah Anda salah mengeksekusi kode yang menganggap bahwa itu mengakses data yang berhasil dibaca, padahal sebenarnya ini tidak pernah terjadi.sumber
feof()
tidak "bertanya pada sistem I / O apakah memiliki lebih banyak data".feof()
, Menurut (Linux) halaman manual : "tes akhir-of-file indikator aliran yang ditunjukkan oleh aliran, kembali nol jika sudah diatur." (juga, panggilan eksplisit keclearerr()
adalah satu-satunya cara untuk mengatur ulang indikator ini); Dalam hal ini, jawaban William Pursell jauh lebih baik.Itu salah karena (jika tidak ada kesalahan baca) itu memasuki loop sekali lagi dari yang diharapkan penulis. Jika ada kesalahan baca, loop tidak pernah berakhir.
Pertimbangkan kode berikut:
Program ini akan secara konsisten mencetak satu lebih besar dari jumlah karakter dalam aliran input (dengan asumsi tidak ada kesalahan baca). Pertimbangkan kasus di mana aliran input kosong:
Dalam hal ini,
feof()
dipanggil sebelum data apa pun dibaca, sehingga mengembalikan false. Loop dimasukkan,fgetc()
disebut (dan kembaliEOF
), dan jumlah bertambah. Kemudianfeof()
dipanggil dan mengembalikan true, menyebabkan loop dibatalkan.Ini terjadi dalam semua kasus seperti itu.
feof()
tidak mengembalikan true sampai setelah membaca pada aliran menemui akhir file. Tujuanfeof()
BUKAN untuk memeriksa apakah pembacaan berikutnya akan mencapai akhir file. Tujuannyafeof()
adalah untuk membedakan antara kesalahan baca dan telah mencapai akhir file. Jikafread()
mengembalikan 0, Anda harus menggunakanfeof
/ferror
untuk memutuskan apakah ada kesalahan atau jika semua data dikonsumsi. Demikian pula jikafgetc
kembaliEOF
.feof()
hanya berguna setelah ketakutan kembali nol ataufgetc
kembaliEOF
. Sebelum itu terjadi,feof()
akan selalu mengembalikan 0.Itu selalu perlu untuk memeriksa nilai balik dari suatu pembacaan (baik suatu
fread()
, ataufscanf()
, atau suatufgetc()
) sebelum memanggilfeof()
.Lebih buruk lagi, pertimbangkan kasus di mana kesalahan baca terjadi. Dalam hal itu,
fgetc()
mengembalikanEOF
,feof()
mengembalikan false, dan loop tidak pernah berakhir. Dalam semua kasus di manawhile(!feof(p))
digunakan, harus ada setidaknya pemeriksaan di dalam loop untukferror()
, atau setidaknya kondisi sementara harus diganti denganwhile(!feof(p) && !ferror(p))
atau ada kemungkinan yang sangat nyata dari loop tak terbatas, mungkin memuntahkan semua jenis sampah sebagai data yang tidak valid sedang diproses.Jadi, secara ringkas, walaupun saya tidak dapat menyatakan dengan pasti bahwa tidak pernah ada situasi di mana secara semantik benar untuk menulis "
while(!feof(f))
" (walaupun harus ada pemeriksaan lain di dalam loop dengan istirahat untuk menghindari loop tak terbatas pada kesalahan baca. ), ini adalah kasus yang hampir pasti selalu salah. Dan bahkan jika suatu kasus pernah muncul di mana itu akan benar, itu sangat salah secara idiom sehingga tidak akan menjadi cara yang tepat untuk menulis kode. Siapa pun yang melihat kode itu harus segera ragu dan berkata, "itu bug". Dan mungkin menampar penulis (kecuali jika penulis adalah bos Anda dalam hal kebijaksanaan disarankan.)sumber
feof(file) || ferror(file)
, jadi sangat berbeda. Tetapi pertanyaan ini tidak dimaksudkan untuk diterapkan pada C ++.Tidak, itu tidak selalu salah. Jika kondisi loop Anda adalah "sementara kami belum mencoba membaca file yang sudah lewat" maka Anda gunakan
while (!feof(f))
. Namun ini bukan kondisi loop umum - biasanya Anda ingin menguji sesuatu yang lain (seperti "dapatkah saya membaca lebih lanjut").while (!feof(f))
tidak salah, itu hanya digunakan salah.sumber
f = fopen("A:\\bigfile"); while (!feof(f)) { /* remove diskette */ }
atau (akan menguji ini)f = fopen(NETWORK_FILE); while (!feof(f)) { /* unplug network cable */ }
while(!eof(f))
feof
bukan tentang mendeteksi akhir file; ini tentang menentukan apakah pembacaan singkat karena kesalahan atau karena input habis.feof()
menunjukkan jika seseorang telah mencoba membaca melewati akhir file. Itu artinya memiliki sedikit efek prediksi: jika itu benar, Anda yakin bahwa operasi input berikutnya akan gagal (Anda tidak yakin yang sebelumnya gagal BTW), tetapi jika itu salah, Anda tidak yakin input berikutnya operasi akan berhasil. Terlebih lagi, operasi input mungkin gagal karena alasan lain selain akhir file (kesalahan format untuk input yang diformat, kegagalan IO murni - kegagalan disk, batas waktu jaringan - untuk semua jenis input), sehingga bahkan jika Anda dapat memperkirakan tentang akhir file (dan siapa pun yang telah mencoba menerapkan Ada satu, yang dapat diprediksi, akan memberi tahu Anda itu bisa rumit jika Anda perlu melewati spasi, dan bahwa ia memiliki efek yang tidak diinginkan pada perangkat interaktif - kadang-kadang memaksa input dari yang berikutnya baris sebelum memulai penanganan yang sebelumnya),Jadi idiom yang benar dalam C adalah untuk mengulang dengan keberhasilan operasi IO sebagai kondisi loop, dan kemudian menguji penyebab kegagalan. Contohnya:
sumber
else
tidak mungkin dengansizeof(line) >= 2
danfgets(line, sizeof(line), file)
tetapi mungkin dengan patologissize <= 0
danfgets(line, size, file)
. Mungkin bahkan mungkin dengansizeof(line) == 1
.feof(f)
tidak MEMPREDIKSI apa pun. Ini menyatakan bahwa operasi SEBELUMNYA telah mencapai akhir file. Tidak lebih, tidak kurang. Dan jika tidak ada operasi sebelumnya (hanya membukanya), itu tidak melaporkan akhir file bahkan jika file itu kosong untuk memulai. Jadi, terlepas dari penjelasan konkurensi dalam jawaban lain di atas, saya tidak berpikir ada alasan untuk tidak melanjutkanfeof(f)
.feof()
sangat tidak intuitif. Menurut pendapat saya yang sangat rendah hati,FILE
status file akhir harus ditetapkantrue
jika operasi membaca apa pun menghasilkan akhir file tercapai. Sebagai gantinya, Anda harus memeriksa secara manual apakah akhir file telah tercapai setelah setiap operasi baca. Misalnya, sesuatu seperti ini akan berfungsi jika membaca dari file teks menggunakanfgetc()
:Alangkah baiknya jika sesuatu seperti ini akan bekerja sebagai gantinya:
sumber
printf("%c", fgetc(in));
? Itu perilaku yang tidak terdefinisi.fgetc()
kembaliint
, tidakchar
.while( (c = getchar()) != EOF)
adalah "sesuatu seperti ini".while( (c = getchar()) != EOF)
bekerja pada salah satu desktop saya yang menjalankan GNU C 10.1.0, tetapi gagal pada Raspberry Pi 4 saya yang menjalankan GNU C 9.3.0. Pada RPi4 saya, itu tidak mendeteksi akhir file, dan terus berjalan.char c
keint c
karya! Terima kasih!!