COMS 3157 Advanced Programming

09 - I/O

Reading

Standard I/O

The C standard library automatically provides every running program with 3 I/O channels:

<stdio.h> contains prototypes for standard I/O functions such as printf(), scanf(), and many many more (see K&R2, appendix B).

Redirection

You can have stdin come from a file instead of the keyboard:

./isort < file_containing_a_number

And have stdout go to a file instead of the screen:

./isort > my_sorting_result

Use 2> to redirect stderr:

./myprogram 2> myerrors

Use >> to append to an existing file:

./myprogram  >> myoutput_of_multiple_runs

./myprogram 2>> myerrors_of_multiple_runs

Use 2>&1 to have stderr go to the same place that stdout` is going to:

./myprogram > my_output_and_errors 2>&1

Pipelines

A pipe connects stdout of one program to stdin of another program:

prog1 | prog2 | prog3

You can throw redirections in there too:

prog1 < input_file | prog2 | prog3 > output_file

Or equivalently:

cat input_file | prog1 | prog2 | prog3 > output_file

You can include prog1’s stderr in the flow like this:

cat input_file | prog1 2>&1 | prog2 | prog3 > output_file

[A little demo of manipulating isort output.]

Formatted I/O

int scanf(const char *format, ...)
int n;
double x;
char a[100];

scanf("%d", &n);

scanf("%lf", &x);

scanf("%s", a);  // Never do this; it's UNSAFE!

scanf("%99s", a);  // Now it's safe, but ugly.
int printf(const char *format, ...)
        %d: int
        %u: unsigned int
        
        %ld: long
        %lu: unsigned long

        %s: string (char *)

        %f: double
        %g: double (trailing zeros not printed)

        %p: memory address (void *)

scanf() and printf() take variable number of arguments. Such functions are called variadic functions, and their argument list ends with “…”

int sscanf(const char *input_string, const char *format, ...)
int sprintf(char *output_buffer, const char *format, ...)
int snprintf(char *output_buffer, size_t size, const char *format, ...)

File I/O

EOF

FILE *fopen(const char *filename, const char *mode)
char *fgets(char *buffer, int size, FILE *file)
int fputs(const char *str, FILE *file)
int fscanf(FILE *file, const char *format, ...)
int fprintf(FILE *file, const char *format, ...)
fclose(FILE *file)

stdin, stdout, stderr

Example:

/*
  * ncat <file_name>
  *
  *   - reads a file line-by-line, 
  *     printing them out with line numbers
  */

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

int main(int argc, char **argv)
{
    if (argc != 2) {
        fprintf(stderr, "%s\n", "usage: ncat <file_name>");
        exit(1);
    }

    char *filename = argv[1];
    FILE *fp = fopen(filename, "r");
    if (fp == NULL) {
        perror(filename);
        exit(1);
    }

    char buf[100];
    int lineno = 1;
    while (fgets(buf, sizeof(buf), fp) != NULL) {
        printf("%4d ", lineno++);
        if (fputs(buf, stdout) == EOF) {
            perror("can't write to stdout");
            exit(1);
        }
    }

    if (ferror(fp)) {
        perror(filename);
        exit(1);
    }

    fclose(fp);
    return 0;
}

Buffering on standard I/O

Three types of buffering:

  1. Unbuffered - stderr
  2. Line-buffered - stdout when it’s connected to a terminal screen
  3. Block-buffered - all other files

fflush(fp) manually flushes the buffer for fp (which is a FILE *).

setbuf(fp, NULL) turns off buffering for fp.

Using standard I/O for binary files

Add 'b' to mode parameter in fopen():

FILE *fp = fopen(filename, "rb");
FILE *fp = fopen(filename, "wb");
...
int fseek(FILE *file, long offset, int whence)
size_t fread(void *p, size_t size, size_t n, FILE *file)
size_t fwrite(const void *p, size_t size, size_t n, FILE *file)