ทำไม“ ในขณะที่ (! feof (ไฟล์))” ผิดเสมอ


573

ฉันเห็นคนพยายามอ่านไฟล์แบบนี้ในโพสต์จำนวนมากเมื่อเร็ว ๆ นี้:

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

เกิดอะไรขึ้นกับลูปนี้



คำตอบ:


453

ฉันต้องการให้มุมมองที่เป็นนามธรรมและระดับสูง

การเกิดขึ้นพร้อมกันและพร้อมกัน

การดำเนินการ I / O โต้ตอบกับสภาพแวดล้อม สภาพแวดล้อมไม่ได้เป็นส่วนหนึ่งของโปรแกรมของคุณและไม่อยู่ภายใต้การควบคุมของคุณ สภาพแวดล้อมมีอยู่จริง "พร้อมกัน" กับโปรแกรมของคุณ เช่นเดียวกับทุกสิ่งที่เกิดขึ้นพร้อมกันคำถามเกี่ยวกับ "สถานะปัจจุบัน" ไม่สมเหตุสมผล: ไม่มีแนวคิดเรื่อง "พร้อมกัน" ข้ามเหตุการณ์ที่เกิดขึ้นพร้อมกัน คุณสมบัติของรัฐจำนวนมากก็ไม่ได้อยู่พร้อมกัน

ให้ฉันทำสิ่งนี้ให้แม่นยำยิ่งขึ้น: สมมติว่าคุณต้องการถามว่า "คุณมีข้อมูลมากขึ้นไหม" คุณสามารถถามคอนเทนเนอร์ที่เกิดขึ้นพร้อมกันหรือระบบ I / O ของคุณ แต่โดยทั่วไปแล้วคำตอบนั้นไม่สามารถโต้ตอบได้และไม่มีความหมาย ดังนั้นถ้าคอนเทนเนอร์บอกว่า "ใช่" - ตามเวลาที่คุณลองอ่านมันอาจไม่มีข้อมูลอีกต่อไป ในทำนองเดียวกันหากคำตอบคือ "ไม่" ตามเวลาที่คุณลองอ่านข้อมูลอาจมาถึงแล้ว บทสรุปก็คือมีเพียงเป็นไม่มีคุณสมบัติเช่น "ฉันมีข้อมูล" เนื่องจากคุณไม่สามารถตอบสนองต่อคำตอบที่เป็นไปได้อย่างมีความหมาย (สถานการณ์จะดีขึ้นเล็กน้อยเมื่อใส่บัฟเฟอร์ที่คุณอาจได้รับ "ใช่ฉันมีข้อมูล" ซึ่งถือเป็นการรับประกันบางประเภท แต่คุณก็ยังต้องสามารถรับมือกับกรณีตรงข้ามได้และเอาท์พุทสถานการณ์ แน่นอนว่าไม่ดีเท่าที่ฉันอธิบาย: คุณไม่มีทางรู้ว่าดิสก์หรือบัฟเฟอร์เครือข่ายนั้นเต็ม)

ดังนั้นเราจึงสรุปได้ว่าเป็นไปไม่ได้และในความเป็นจริงไม่สมเหตุสมผลเพื่อถามระบบ I / O ว่าจะสามารถดำเนินการ I / O ได้หรือไม่ วิธีเดียวที่เป็นไปได้ที่เราสามารถโต้ตอบกับมัน (เช่นเดียวกับคอนเทนเนอร์ที่เกิดขึ้นพร้อมกัน) คือการพยายามดำเนินการและตรวจสอบว่าสำเร็จหรือล้มเหลว ในช่วงเวลานั้นที่คุณมีปฏิสัมพันธ์กับสิ่งแวดล้อมคุณจะรู้ได้ว่าการโต้ตอบนั้นเป็นไปได้จริงหรือไม่และ ณ จุดนั้นคุณต้องกระทำการโต้ตอบ (นี่คือ "จุดประสาน" หากคุณต้องการ)

EOF

ตอนนี้เราไปถึง EOF EOF คือการตอบสนองที่คุณได้รับจากการดำเนินการ I / O ที่พยายามดำเนินการ หมายความว่าคุณกำลังพยายามอ่านหรือเขียนบางสิ่ง แต่เมื่อทำเช่นนั้นคุณไม่สามารถอ่านหรือเขียนข้อมูลใด ๆ ได้และพบจุดสิ้นสุดของอินพุตหรือเอาต์พุตแทน สิ่งนี้เป็นจริงสำหรับ I / O API ทั้งหมดไม่ว่าจะเป็นไลบรารีมาตรฐาน C, C ++ iostreams หรือไลบรารีอื่น ๆ ตราบใดที่การดำเนินงานของ I / O ประสบความสำเร็จคุณก็ไม่สามารถรู้ได้ว่าการดำเนินงานในอนาคตจะสำเร็จหรือไม่ คุณต้องลองใช้การดำเนินการก่อนแล้วจึงตอบสนองต่อความสำเร็จหรือความล้มเหลว

ตัวอย่าง

ในแต่ละตัวอย่างที่ทราบอย่างระมัดระวังว่าเราเป็นครั้งแรกพยายาม I / O การทำงานและจากนั้นกินผลถ้ามันถูกต้อง หมายเหตุต่อไปว่าเรามักจะต้องใช้ผลของการดำเนินการ I / O แต่ผลที่จะเกิดรูปร่างที่แตกต่างและรูปแบบในแต่ละตัวอย่าง

  • C stdio อ่านจากไฟล์:

    for (;;) {
        size_t n = fread(buf, 1, bufsize, infile);
        consume(buf, n);
        if (n < bufsize) { break; }
    }

    ผลลัพธ์ที่เราต้องใช้คือnจำนวนองค์ประกอบที่อ่าน (ซึ่งอาจน้อยเท่ากับศูนย์)

  • C stdio, scanf:

    for (int a, b, c; scanf("%d %d %d", &a, &b, &c) == 3; ) {
        consume(a, b, c);
    }

    ผลลัพธ์ที่เราต้องใช้คือค่าส่งคืนของscanfจำนวนองค์ประกอบที่ถูกแปลง

  • การแยกรูปแบบ c ++, iostreams:

    for (int n; std::cin >> n; ) {
        consume(n);
    }

    ผลลัพธ์ที่เราต้องใช้คือstd::cinตัวของมันเองซึ่งสามารถประเมินได้ในบริบทบูลีนและบอกเราว่ากระแสยังอยู่ในgood()สถานะหรือไม่

  • C ++, iostreams getline:

    for (std::string line; std::getline(std::cin, line); ) {
        consume(line);
    }

    ผลลัพธ์ที่เราต้องใช้คืออีกครั้งstd::cinเหมือนเมื่อก่อน

  • POSIX write(2)เพื่อล้างบัฟเฟอร์:

    char const * p = buf;
    ssize_t n = bufsize;
    for (ssize_t k = bufsize; (k = write(fd, p, n)) > 0; p += k, n -= k) {}
    if (n != 0) { /* error, failed to write complete buffer */ }

    ผลลัพธ์ที่เราใช้ที่นี่คือkจำนวนไบต์ที่เขียน จุดนี้คือเราสามารถรู้ได้ว่ามีกี่ไบต์เท่านั้นที่ถูกเขียนหลังจากการเขียน

  • POSIX getline()

    char *buffer = NULL;
    size_t bufsiz = 0;
    ssize_t nbytes;
    while ((nbytes = getline(&buffer, &bufsiz, fp)) != -1)
    {
        /* Use nbytes of data in buffer */
    }
    free(buffer);

    ผลลัพธ์ที่เราต้องใช้คือnbytesจำนวนไบต์สูงสุดและรวมถึงการขึ้นบรรทัดใหม่ (หรือ EOF หากไฟล์ไม่ได้ลงท้ายด้วยการขึ้นบรรทัดใหม่)

    โปรดทราบว่าฟังก์ชันส่งกลับอย่างชัดเจน-1(ไม่ใช่ EOF!) เมื่อเกิดข้อผิดพลาดหรือถึง EOF

คุณอาจสังเกตว่าเราไม่ค่อยสะกดคำที่แท้จริง "EOF" เรามักจะตรวจสอบเงื่อนไขข้อผิดพลาดด้วยวิธีอื่นที่น่าสนใจสำหรับเราในทันที (เช่นไม่สามารถทำ I / O ได้มากเท่าที่เราต้องการ) ในทุกตัวอย่างมีคุณสมบัติ API บางอย่างที่สามารถบอกเราได้อย่างชัดเจนว่าสถานะ EOF นั้นเกิดขึ้นจริง ๆ แล้ว แต่นี่ไม่ใช่ข้อเท็จจริงของข้อมูลที่มีประโยชน์มาก มันมีรายละเอียดมากกว่าที่เรามักจะใส่ใจ สิ่งสำคัญคือว่า I / O ประสบความสำเร็จมากกว่านั้นล้มเหลว

  • ตัวอย่างสุดท้ายที่เคียวรีสถานะ EOF จริง: สมมติว่าคุณมีสตริงและต้องการทดสอบว่ามันหมายถึงจำนวนเต็มเต็มจำนวนโดยไม่มีบิตพิเศษที่ส่วนท้ายยกเว้นช่องว่าง ใช้ C ++ iostreams มันจะเป็นดังนี้:

    std::string input = "   123   ";   // example
    
    std::istringstream iss(input);
    int value;
    if (iss >> value >> std::ws && iss.get() == EOF) {
        consume(value);
    } else {
        // error, "input" is not parsable as an integer
    }

    เราใช้สองผลลัพธ์ที่นี่ ประการแรกคือissวัตถุกระแสตัวเองเพื่อตรวจสอบว่าการแยกรูปแบบที่จะvalueประสบความสำเร็จ แต่หลังจากใช้พื้นที่ว่างมากiss.get()เกินไปเราก็ดำเนินการ I / O / การดำเนินการอีกครั้งและคาดว่ามันจะล้มเหลวในฐานะ EOF ซึ่งเป็นกรณีที่สตริงทั้งหมดถูกใช้โดยการแตกไฟล์ที่จัดรูปแบบแล้ว

    ในไลบรารีมาตรฐาน C คุณสามารถบรรลุสิ่งที่คล้ายกับstrto*lฟังก์ชั่นโดยการตรวจสอบว่าตัวชี้สิ้นสุดได้ถึงจุดสิ้นสุดของสายป้อน

คำตอบ

while(!feof)ผิดเพราะทดสอบสิ่งที่ไม่เกี่ยวข้องและไม่สามารถทดสอบสิ่งที่คุณต้องรู้ ผลลัพธ์คือคุณกำลังรันโค้ดอย่างไม่ถูกต้องซึ่งถือว่าเป็นการเข้าถึงข้อมูลที่อ่านได้สำเร็จเมื่อจริง ๆ แล้วมันไม่เคยเกิดขึ้น


34
@ เซียปัน: ฉันไม่คิดว่ามันเป็นเรื่องจริง ทั้ง C99 และ C11 อนุญาตสิ่งนี้
Kerrek SB

11
แต่ ANSI C ไม่ได้
CiaPan

3
@ JonathanMee: มันไม่ดีด้วยเหตุผลทั้งหมดที่ฉันพูดถึง: คุณไม่สามารถมองไปในอนาคต คุณไม่สามารถบอกได้ว่าจะเกิดอะไรขึ้นในอนาคต
Kerrek SB

3
@JonathanMee: ใช่ว่าจะเหมาะสม แต่โดยปกติแล้วคุณสามารถรวมการตรวจสอบนี้เข้ากับการดำเนินการ (เนื่องจากการดำเนินการ iostreams ส่วนใหญ่จะส่งคืนวัตถุกระแสซึ่งตัวเองมีการแปลงบูลีน) และวิธีที่คุณทำให้ชัดเจนว่าคุณไม่ ไม่สนใจค่าส่งคืน
Kerrek SB

4
ย่อหน้าที่สามนั้นทำให้เข้าใจผิด / ไม่ถูกต้องอย่างน่าทึ่งสำหรับคำตอบที่ได้รับการยอมรับและสูง feof()ไม่ "ถามระบบ I / O ว่ามีข้อมูลมากกว่านี้" feof()ตามmanpage (Linux) : "ทดสอบตัวบ่งชี้สิ้นสุดไฟล์สำหรับสตรีมที่ชี้ไปตามกระแสโดยส่งคืนค่าที่ไม่ใช่ศูนย์หากตั้งค่าไว้" (เช่นกันการโทรที่ชัดเจนถึงclearerr()เป็นวิธีเดียวในการรีเซ็ตตัวบ่งชี้นี้) ในแง่นี้คำตอบของ William Pursell นั้นดีกว่ามาก
Arne Vogel

234

มันผิดเพราะ (ในกรณีที่ไม่มีข้อผิดพลาดในการอ่าน) มันจะเข้าสู่ลูปมากกว่าหนึ่งครั้งที่ผู้เขียนคาดไว้ หากมีข้อผิดพลาดในการอ่านลูปจะไม่สิ้นสุด

พิจารณารหัสต่อไปนี้:

/* WARNING: demonstration of bad coding technique!! */

#include <stdio.h>
#include <stdlib.h>

FILE *Fopen(const char *path, const char *mode);

int main(int argc, char **argv)
{
    FILE *in;
    unsigned count;

    in = argc > 1 ? Fopen(argv[1], "r") : stdin;
    count = 0;

    /* WARNING: this is a bug */
    while( !feof(in) ) {  /* This is WRONG! */
        fgetc(in);
        count++;
    }
    printf("Number of characters read: %u\n", count);
    return EXIT_SUCCESS;
}

FILE * Fopen(const char *path, const char *mode)
{
    FILE *f = fopen(path, mode);
    if( f == NULL ) {
        perror(path);
        exit(EXIT_FAILURE);
    }
    return f;
}

โปรแกรมนี้จะพิมพ์หนึ่งตัวอักษรมากกว่าจำนวนอักขระในอินพุตสตรีมอย่างต่อเนื่อง (สมมติว่าไม่มีข้อผิดพลาดในการอ่าน) พิจารณากรณีที่สตรีมอินพุตว่าง:

$ ./a.out < /dev/null
Number of characters read: 1

ในกรณีนี้feof()จะถูกเรียกใช้ก่อนที่จะอ่านข้อมูลใด ๆ ดังนั้นจึงส่งคืนค่าเท็จ ลูปถูกป้อนfgetc()เรียกว่า (และส่งคืนEOF) และจำนวนจะเพิ่มขึ้น จากนั้นfeof()จะถูกเรียกใช้และส่งกลับค่าจริงทำให้ลูปยกเลิก

สิ่งนี้เกิดขึ้นในทุกกรณี feof()ไม่ส่งคืนจริงจนกว่าหลังจากที่อ่านบนสตรีมพบจุดสิ้นสุดของไฟล์ วัตถุประสงค์ของการfeof()ไม่ตรวจสอบว่าการอ่านครั้งต่อไปจะมาถึงจุดสิ้นสุดของไฟล์หรือไม่ วัตถุประสงค์feof()คือเพื่อแยกความแตกต่างระหว่างข้อผิดพลาดในการอ่านและถึงจุดสิ้นสุดของไฟล์ หากfread()ส่งคืน 0 คุณต้องใช้feof/ ferrorเพื่อตัดสินใจว่าจะเกิดข้อผิดพลาดหรือข้อมูลทั้งหมดถูกใช้ไปแล้ว ในทำนองเดียวกันถ้าผลตอบแทน fgetc มีประโยชน์เฉพาะหลังจากที่ fread คืนค่าศูนย์หรือคืนค่าแล้ว ก่อนที่จะเกิดขึ้นEOFfeof()fgetcEOFfeof()จะส่งคืน 0 เสมอ

จำเป็นต้องตรวจสอบค่าส่งคืนของการอ่าน (อย่างใดอย่างหนึ่งfread()หรือfscanf()หรือfgetc()) ก่อนที่จะโทรfeof()ก่อนที่จะเรียก

ยิ่งแย่กว่านั้นให้พิจารณากรณีที่เกิดข้อผิดพลาดในการอ่าน ในกรณีที่, fgetc()ผลตอบแทนEOF, feof()ผลตอบแทนเท็จและห่วงไม่เคยสิ้นสุด ในทุกกรณีที่while(!feof(p))มีการใช้งานจะต้องมีการตรวจสอบภายในลูปสำหรับferror()อย่างน้อยที่สุดหรืออย่างน้อยที่สุดควรจะแทนที่ด้วยเงื่อนไข while while(!feof(p) && !ferror(p))หรือมีความเป็นไปได้ที่แท้จริงของลูปที่ไม่สิ้นสุดซึ่งอาจทำให้ขยะทุกประเภทเป็น กำลังประมวลผลข้อมูลที่ไม่ถูกต้อง

ดังนั้นโดยสรุปแม้ว่าฉันไม่สามารถระบุด้วยความมั่นใจได้ว่าจะไม่มีสถานการณ์ที่อาจจะถูกต้องในการเขียน " while(!feof(f))" (แม้ว่าจะต้องมีการตรวจสอบอีกครั้งภายในลูปพร้อมกับตัวแบ่งเพื่อหลีกเลี่ยงการวนซ้ำไม่สิ้นสุดในข้อผิดพลาดการอ่าน ) เป็นกรณีที่เกือบผิดเสมอ และแม้ว่ากรณีจะเกิดขึ้นที่ใดก็ตามที่มันถูกต้องมันเป็นเรื่องผิดปกติที่ไม่น่าจะเป็นวิธีที่ถูกต้องในการเขียนรหัส ใครก็ตามที่เห็นรหัสนั้นควรลังเลทันทีและพูดว่า "นั่นเป็นข้อผิดพลาด" และอาจตบผู้เขียน (เว้นแต่ผู้แต่งคือหัวหน้าของคุณในกรณีที่แนะนำให้ใช้ดุลยพินิจ)


7
แน่นอนว่ามันผิด - แต่นอกเหนือจากนั้นมันไม่ได้ "น่าเกลียดน่าเกลียด"
nobar

89
คุณควรเพิ่มตัวอย่างของรหัสที่ถูกต้องเนื่องจากฉันคิดว่าผู้คนจำนวนมากจะมาที่นี่เพื่อหาวิธีแก้ไขอย่างรวดเร็ว
jleahy

6
@Thomas: ฉันไม่ใช่ผู้เชี่ยวชาญ C ++ แต่ฉันเชื่อว่า file.eof () จะให้ผลลัพธ์เหมือนfeof(file) || ferror(file)กันดังนั้นจึงแตกต่างกันมาก แต่คำถามนี้ไม่ได้มีวัตถุประสงค์เพื่อใช้กับ C ++
วิลเลียม Pursell

6
@ m-ric ที่ไม่ถูกต้องเช่นกันเพราะคุณยังคงพยายามประมวลผลการอ่านที่ล้มเหลว
Mark Ransom

4
นี่คือคำตอบที่ถูกต้องจริง feof () ใช้เพื่อทราบผลลัพธ์ของการพยายามอ่านครั้งก่อน ดังนั้นคุณอาจไม่ต้องการใช้มันเป็นเงื่อนไขการแบ่งลูปของคุณ +1
แจ็ค

63

ไม่มันไม่ผิดเสมอไป ถ้าเงื่อนไขห่วงของคุณคือ "ในขณะที่เรายังไม่ได้พยายามที่จะอ่านตอนท้ายที่ผ่านมาของไฟล์" while (!feof(f))แล้วคุณใช้ อย่างไรก็ตามนี่ไม่ใช่เงื่อนไขลูปทั่วไป - โดยปกติแล้วคุณต้องการทดสอบอย่างอื่น (เช่น "ฉันสามารถอ่านเพิ่มเติม") while (!feof(f))ไม่ผิดมันแค่ใช้ผิด


1
ฉันสงสัยว่า ... f = fopen("A:\\bigfile"); while (!feof(f)) { /* remove diskette */ }หรือ (ทดสอบสิ่งนี้)f = fopen(NETWORK_FILE); while (!feof(f)) { /* unplug network cable */ }
pmg

1
@ pmg: ตามที่กล่าวไว้ว่า "ไม่ใช่เงื่อนไขลูปทั่วไป" ฮิฮิ ฉันไม่สามารถจริงๆคิดว่ากรณีใด ๆ ฉันจำเป็นต้องใช้มันมักจะฉันสนใจ "ผมได้อ่านสิ่งที่ฉันต้องการ" กับทุกสิ่งที่หมายถึงการจัดการข้อผิดพลาด
เอริค

@pmg: ตามที่กล่าวไว้คุณไม่ค่อยต้องการ while(!eof(f))
Erik

9
อย่างแม่นยำยิ่งขึ้นเงื่อนไขคือ "ในขณะที่เราไม่ได้พยายามอ่านที่ส่วนท้ายของไฟล์และไม่มีข้อผิดพลาดในการอ่าน" feofไม่เกี่ยวกับการตรวจหาจุดสิ้นสุดไฟล์ มันเกี่ยวกับการพิจารณาว่าการอ่านสั้นเนื่องจากข้อผิดพลาดหรือเพราะอินพุตหมด
วิลเลียม Pursell

35

feof()ระบุว่ามีผู้พยายามอ่านไฟล์จนจบไฟล์ นั่นหมายความว่าจะมีผลการทำนายเล็กน้อย: ถ้าเป็นจริงคุณแน่ใจว่าการดำเนินการอินพุตถัดไปจะล้มเหลว (คุณไม่แน่ใจว่าก่อนหน้านี้ล้มเหลว BTW) แต่ถ้ามันเป็นเท็จคุณไม่แน่ใจว่าอินพุตถัดไป การดำเนินการจะประสบความสำเร็จ ยิ่งกว่านั้นการดำเนินการอินพุตอาจล้มเหลวด้วยเหตุผลอื่นนอกเหนือจากจุดสิ้นสุดไฟล์ (ข้อผิดพลาดของรูปแบบสำหรับอินพุตที่จัดรูปแบบ, ความล้มเหลวของ IO บริสุทธิ์ - ความล้มเหลวของดิสก์, หมดเวลาเครือข่าย - สำหรับชนิดอินพุตทั้งหมด) ดังนั้นแม้ว่าคุณจะคาดเดาได้ จุดสิ้นสุดของไฟล์ (และใครก็ตามที่พยายามใช้ Ada one ซึ่งเป็นการคาดการณ์จะบอกได้ว่ามันซับซ้อนหากคุณต้องการข้ามช่องว่างและมันมีผลกระทบที่ไม่พึงประสงค์บนอุปกรณ์อินเทอร์แอคทีฟ - บางครั้งบังคับให้อินพุทถัดไป บรรทัดก่อนที่จะเริ่มการจัดการของก่อนหน้านี้)

ดังนั้นสำนวนที่ถูกต้องใน C คือการวนซ้ำกับความสำเร็จในการดำเนินการของ IO เป็นเงื่อนไขลูปแล้วทดสอบสาเหตุของความล้มเหลว ตัวอย่างเช่น

while (fgets(line, sizeof(line), file)) {
    /* note that fgets don't strip the terminating \n, checking its
       presence allow to handle lines longer that sizeof(line), not showed here */
    ...
}
if (ferror(file)) {
   /* IO failure */
} else if (feof(file)) {
   /* format error (not possible with fgets, but would be with fscanf) or end of file */
} else {
   /* format error (not possible with fgets, but would be with fscanf) */
}

2
การไปยังจุดสิ้นสุดของไฟล์ไม่ใช่ข้อผิดพลาดดังนั้นฉันจึงตั้งคำถามว่า "การดำเนินการป้อนข้อมูลอาจล้มเหลวด้วยเหตุผลอื่นนอกเหนือจากจุดสิ้นสุดไฟล์"
William Pursell

@WilliamPursell การเข้าถึง eof ไม่จำเป็นต้องเป็นข้อผิดพลาด แต่ไม่สามารถทำการดำเนินการอินพุตได้เนื่องจาก eof เป็นหนึ่ง และเป็นไปไม่ได้ใน C ในการตรวจสอบ eof ที่เชื่อถือได้โดยไม่ทำให้การป้อนข้อมูลล้มเหลว
AProgrammer

เห็นด้วยที่ผ่านมาelseไม่ได้กับsizeof(line) >= 2และfgets(line, sizeof(line), file)แต่เป็นไปได้ที่มีพยาธิสภาพและsize <= 0 แม้อาจจะเป็นไปได้ด้วยfgets(line, size, file) sizeof(line) == 1
chux - Reinstate Monica

1
ทั้งหมดที่ "การคาดการณ์คุณค่า" พูดคุย ... ฉันไม่เคยคิดถึงมันแบบนั้น ในโลกของฉันfeof(f)ไม่ได้คาดการณ์อะไรเลย มันระบุว่าการดำเนินการก่อนหน้านี้ได้กดจุดสิ้นสุดของไฟล์ ไม่มีอะไรเพิ่มเติมไม่น้อยไปกว่านี้ และหากไม่มีการดำเนินการก่อนหน้า (เพิ่งเปิด) มันจะไม่รายงานจุดสิ้นสุดไฟล์แม้ว่าไฟล์นั้นจะว่างเปล่าเพื่อเริ่มต้น feof(f)ดังนั้นนอกเหนือจากคำอธิบายที่เห็นพ้องด้วยในคำตอบอื่นข้างต้นผมไม่คิดว่ามีเหตุผลใดที่จะไม่ห่วง
BitTickler

@AProgrammer: คำขอ "อ่านไม่เกิน N ไบต์" ที่ให้ผลเป็นศูนย์ไม่ว่าจะเป็นเพราะ EOF "ถาวร" หรือเพราะยังไม่มีข้อมูลอีกเลยก็ไม่ใช่ข้อผิดพลาด ในขณะที่ feof () อาจจะไม่น่าเชื่อถือคาดการณ์ว่าการร้องขอในอนาคตจะให้ข้อมูลก็อาจเชื่อถือได้ระบุว่าการร้องขอในอนาคตจะไม่ บางทีอาจจะมีควรจะเป็นฟังก์ชั่นสถานะที่จะแสดงให้เห็นว่า "มันจะเป็นไปได้ว่าในอนาคตอ่านคำขอจะประสบความสำเร็จ" โดยมีความหมายว่าหลังจากที่ได้อ่านถึงจุดสิ้นสุดของแฟ้มสามัญ, การดำเนินงานที่มีคุณภาพควรจะพูดว่าในอนาคตอ่านไม่น่าจะประสบความสำเร็จขาดเหตุผลบางอย่างไป เชื่อว่าพวกเขาอาจจะ
supercat

0

feof()ไม่ง่ายมาก ในความเห็นที่ต่ำต้อยของฉันสถานะFILEจุดสิ้นสุดของไฟล์ควรถูกตั้งค่าเป็นtrueหากการดำเนินการอ่านใด ๆ เกิดขึ้นเมื่อถึงจุดสิ้นสุดไฟล์ คุณต้องตรวจสอบด้วยตนเองว่าถึงจุดสิ้นสุดของไฟล์หลังจากดำเนินการอ่านแต่ละครั้งหรือไม่ ตัวอย่างเช่นสิ่งนี้จะใช้งานได้หากอ่านจากไฟล์ข้อความโดยใช้fgetc():

#include <stdio.h>

int main(int argc, char *argv[])
{
  FILE *in = fopen("testfile.txt", "r");

  while(1) {
    char c = fgetc(in);
    if (feof(in)) break;
    printf("%c", c);
  }

  fclose(in);
  return 0;
}

มันจะดีถ้ามีอะไรแบบนี้ใช้ได้ผล:

#include <stdio.h>

int main(int argc, char *argv[])
{
  FILE *in = fopen("testfile.txt", "r");

  while(!feof(in)) {
    printf("%c", fgetc(in));
  }

  fclose(in);
  return 0;
}

1
printf("%c", fgetc(in));? นั่นเป็นพฤติกรรมที่ไม่ได้กำหนด fgetc()ผลตอบแทนที่ได้int char
Andrew Henle

สำหรับฉันแล้วมันดูเหมือนสำนวนมาตรฐานwhile( (c = getchar()) != EOF)มาก "อะไรแบบนี้"
William Pursell

while( (c = getchar()) != EOF)ทำงานบนเดสก์ท็อปของฉันที่รัน GNU C 10.1.0 แต่ล้มเหลวใน Raspberry Pi 4 ของฉันที่รัน GNU C 9.3.0 บน RPi4 ของฉันมันตรวจไม่พบจุดสิ้นสุดไฟล์และทำต่อไป
Scott Deagan

@AndrewHenle คุณพูดถูก! เปลี่ยนchar cไปใช้int cงาน! ขอบคุณ !!
Scott Deagan
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.