LABORATORY 12

Input/Output Operations. Files.

Duration: 2 hours Library: stdio.h Previous: Laboratory 11 PDF handout RO versiunea română

Data in memory disappears when the program ends. Files are data structures organized on external storage, which allow information to be kept between runs and exchanged between applications.

1Lab objectives

closedopengood() == trueeof()fail()openend of fileformat errorclose - only now is the data guaranteed to reach the disk
Fig. - States of a file stream. Any operation after an error is ignored until clear(), and written data is guaranteed to be on disk only after closing.
  • Classifying files by content, access, and the type of operations allowed
  • Correctly opening and closing a file, with a success check
  • Choosing the right opening mode
  • Reading and writing text and binary files
  • Positioning within a file for direct access

2File classification

CriterionCategoriesDescription
By contenttext filescharacters organized in lines; end of line marked by <CR><LF>
typed filesrecords of a certain type, elementary or structured
untyped filesbinary files, raw sequences of bytes
By accesssequential accessinformation is processed only in record order
direct accesscomponents can be processed in any order, after positioning
By operationsinputreading only
outputwriting only
input/outputboth - information can be updated
The end markerThe end of a file is signaled by a special marker called EOF (end of file). The reading functions return it to indicate that no more data is available.

3Opening and closing

Every transfer is preceded by the opening operation, which creates a stream and associates it with the file. A variable of type FILE * is used, and a buffer zone through which the transfers happen is allocated automatically.

the correct pattern
FILE *f = fopen("data.txt", "r");       // 1. opening

if (f == NULL) {                        // 2. mandatory CHECK
    printf("Cannot open the file!\n");
    return 1;
}

/* ... reading or writing ... */        // 3. processing

fclose(f);                              // 4. closing
Why closing is mandatoryWritten data sits in a buffer zone and only reaches the disk once it fills up or on fclose. If the program ends without closing, the last data written can be lost. In addition, the number of files open at once is limited.
FunctionRole
fopen(name, mode)opens the file; returns NULL on failure
fclose(f)flushes the buffer and closes the file
feof(f)tests whether the end of the file has been reached
ferror(f)tests whether an error has occurred
fflush(f)forces the buffer to be written to disk
remove(name)deletes the file
rename(old, new)renames the file

4Opening modes

ModeMeaningIf the file existsIf it does not exist
"r"readingopened from the starterror (NULL)
"w"writingall contents are erased!created
"a"appendingwriting happens at the endcreated
"r+"reading and writingopened from the starterror
"w+"reading and writingall contents are erasedcreated
"a+"reading and appendingwriting happens at the endcreated

The letter b is added for binary files ("rb", "wb", "ab") and t for text, which is the default mode.

The most dangerous mode is "w"Opening an existing file with "w" instantly erases all of its contents, even if nothing is written afterward. To append to an existing file, use "a".

5Operations on text files

LevelReadingWriting
Characterfgetc(f), getc(f)fputc(c, f), putc(c, f)
Line / stringfgets(s, n, f)fputs(s, f)
Formattedfscanf(f, "...", ...)fprintf(f, "...", ...)
Correspondence with the console functionsEvery file function has a console equivalent: fprintf ↔ printf, fscanf ↔ scanf, fgets ↔ gets. The difference is the first parameter, which indicates the stream. printf(...) is equivalent to fprintf(stdout, ...).
Watch out for feofThe feof function becomes true only after a failed read, not before. The loop while (!feof(f)) { fscanf(...); ... } processes the last record twice. Correct: while (fscanf(f, "%d", &x) == 1) { ... }

6Operations on binary files

prototypes
size_t fread (void *p, size_t size, size_t nr, FILE *f);
size_t fwrite(const void *p, size_t size, size_t nr, FILE *f);

// p    - the address of the memory zone
// size - the size of one record, in bytes
// nr   - how many records are transferred
// f    - the stream
// returns the number of records actually transferred
AspectText fileBinary file
Contentreadable charactersexact copy of the in-memory representation
The number 123455 characters (5 bytes)4 bytes (an int)
Manual editingpossible in any editorpractically impossible
Speedslower (conversions)fast, no conversions
Portabilitygoodlimited (endianness, padding)

7Direct access - positioning within a file

FunctionRole
fseek(f, offset, origin)moves the position indicator
ftell(f)returns the current position, in bytes
rewind(f)returns to the start of the file
Origin constantReference point
SEEK_SETthe start of the file
SEEK_CURthe current position
SEEK_ENDthe end of the file
Reading record number n fseek(f, n * sizeof(Record), SEEK_SET); followed by fread(...). This is the major advantage of binary files with fixed-size records: any element is accessed directly, with no need to scan the whole file.

8Source code

text_write.c - creating and appending
#include <stdio.h>

int main(void)
{
    FILE *f = fopen("data.txt", "w");     // WARNING: erases existing contents
    if (f == NULL) {
        printf("Cannot create the file!\n");
        return 1;
    }

    fprintf(f, "John Smith;20;9.15\n");
    fprintf(f, "Mary Johnson;21;8.70\n");
    fprintf(f, "Radu Georgescu;20;9.60\n");

    fclose(f);
    printf("File created.\n");

    // appending at the end, without erasing the contents
    f = fopen("data.txt", "a");
    if (f) {
        fprintf(f, "Ana Dumitrescu;22;9.85\n");
        fclose(f);
        printf("Record added.\n");
    }
    return 0;
}
text_read.c - correct traversal
#include <stdio.h>

int main(void)
{
    FILE *f = fopen("data.txt", "r");
    if (f == NULL) {
        printf("The file does not exist!\n");
        return 1;
    }

    char line[200];
    int nrLines = 0;

    // correct version: the condition is precisely the success of the read
    while (fgets(line, sizeof(line), f) != NULL) {
        nrLines++;
        printf("%3d: %s", nrLines, line);
    }

    printf("\nTotal %d lines\n", nrLines);
    fclose(f);

    // formatted reading, field by field
    f = fopen("data.txt", "r");
    char name[50];
    int age;
    float average;

    printf("\nStructured reading:\n");
    while (fscanf(f, "%49[^;];%d;%f\n", name, &age, &average) == 3) {
        printf("  %-20s %3d years  average %.2f\n", name, age, average);
    }
    fclose(f);

    return 0;
}
binary.c - writing and reading structures
#include <stdio.h>

typedef struct {
    char  name[50];
    int   age;
    float average;
} Student;

int main(void)
{
    Student group[3] = {
        {"John Smith",    20, 9.15f},
        {"Mary Johnson",  21, 8.70f},
        {"Radu Georgescu",20, 9.60f}
    };

    // binary write: the whole array, in a single call
    FILE *f = fopen("students.dat", "wb");
    if (!f) return 1;
    fwrite(group, sizeof(Student), 3, f);
    fclose(f);

    // reading record number 1 (the second one), WITHOUT reading the first
    f = fopen("students.dat", "rb");
    if (!f) return 1;

    Student one;
    fseek(f, 1 * sizeof(Student), SEEK_SET);   // direct positioning
    fread(&one, sizeof(Student), 1, f);

    printf("Record 1: %s, %d years, average %.2f\n",
           one.name, one.age, one.average);

    // the size of the file
    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    printf("File size: %ld bytes = %ld records\n",
           size, size / (long)sizeof(Student));

    fclose(f);
    return 0;
}
copy.c - copying a file, byte by byte
#include <stdio.h>

int main(void)
{
    FILE *source = fopen("data.txt", "rb");
    if (!source) { printf("The source is missing\n"); return 1; }

    FILE *dest = fopen("copy.txt", "wb");
    if (!dest) { fclose(source); return 1; }

    char buffer[1024];
    size_t bytesRead;
    long total = 0;

    // copying in blocks, much faster than byte by byte
    while ((bytesRead = fread(buffer, 1, sizeof(buffer), source)) > 0) {
        fwrite(buffer, 1, bytesRead, dest);
        total += bytesRead;
    }

    printf("Copied %ld bytes\n", total);

    fclose(source);
    fclose(dest);
    return 0;
}

9Code workshop

The boxes below have their own file system, which exists only on the page. Files written by one program can be read right afterward by the same program.

Writing to and reading from a text file
#include <stdio.h>

int main(void)
{
    FILE *f;
    int   grade;
    char  name[20];

    /* --- writing --- */
    f = fopen("grades.txt", "w");
    if (f == NULL) { printf("Cannot open the file\n"); return 1; }

    fprintf(f, "Popescu 9\n");
    fprintf(f, "Ionescu 7\n");
    fprintf(f, "Vasilescu 10\n");
    fclose(f);

    /* --- reading --- */
    f = fopen("grades.txt", "r");
    if (f == NULL) { printf("The file does not exist\n"); return 1; }

    printf("File contents:\n");
    while (fscanf(f, "%s %d", name, &grade) == 2)
        printf("  %-12s %2d\n", name, grade);
    fclose(f);
    return 0;
}
Try thisChange the mode from "w" to "a" (append) and run twice: you will see the lines being added at the end instead of replacing the file.
Exercise - reading line by line
#include <stdio.h>

int main(void)
{
    FILE *f = fopen("story.txt", "r");
    char line[100];
    int  nr = 0;

    if (f == NULL) { printf("The file does not exist\n"); return 1; }

    /* Read the file line by line with fgets and print each
       line preceded by its number, in the format shown on the right.
       At the end print how many lines the file has.                  */

    fclose(f);
    return 0;
}
Try thisThe file story.txt already exists in this box's file system.

10Work tasks

  • Create a text file with student data and then read it line by line.
  • Open an existing file with mode "w" and observe what happens to its contents.
  • Add new records using mode "a", without losing the old data.
  • Count the lines, words, and characters in a text file.
  • Write an array of structures to a binary file and read only the third record.
  • Compare the size of the same information saved in text format and in binary format.
  • Implement file copying using a 1024-byte buffer.
  • Test the difference between while (!feof(f)) and while (fscanf(...) == n).

11Extended application

ExtensionImplement a persistent phone book, with a binary file of fixed-size records: adding, searching by name, modifying an existing record (through fseek and rewriting in place, without recreating the file), and logical deletion through an indicator field. Add a "compaction" function that physically removes the records marked as deleted.

12Review questions

13Resources