Each C program comes with three I/O streams:
The input stream is called standard-input (stdin), the usual output stream is called standard-output (stdout), and the side stream of output characters for errors is called standard-error (stderr). Internally they occupy file descriptors 0, 1 and 2 respectively.
This convention permits C programs
to be connected together so that the output stream from one program can
be filtered into the input stream for another via the pipe operator |.
For example:
man -k output | grep printf ls * | sort -rgetchar and putchar
The simplest I/O is to read and write
one character at a time.
#include <stdio.h> /* getchar prototype in here */ #define BLANK ' ' int c; while ( (c = getchar() ) != EOF ) { if ( c != BLANK ) { putchar(c); } }Important points (King p. 121, 498/9, K&R p. 247):
One would expect the simple input/output
functions (getchar/putchar) to be prototyped in the header as:
extern int getchar (void); extern int putchar (char c);The function getchar uses "out of band signalling" to return as EOF a value that cannot possibly be a legitimate character. Since all 8-bit characters are legitimate, the character is returned in a bigger field (e.g. as a 16-bit or 32-bit integer). Thus getchar returns an int whose right-most 8 bits hold the character and whose left-most 8-bits provide the "out-of-band signalling".
The function putchar returns the character just sent to stdout. Although this may seem unusual, it provides consistency with getchar and is also a recognition that in C there are no procedures, just functions. It can also be used as an error indicator, should putchar fail.
When is EOF sensed? After the I/O operation is tried.
scanf and printf
If we want to manipulate more than just one character at a time, we need to use the structured input/output routines.
extern int scanf (const char* format, ... &addrs);
extern int printf (const char* format, ... values);
Here is a program to read integers
and add them up.
#include <stdio.h> #ifndef TRUE #define TRUE 1 #endif int main (void) { int cur_value; int sum = 0; int rcode; while( TRUE ) { rcode = scanf ("%d", &cur_value); if ( rcode == 0 ) { fprintf (stderr, "Received an invalid input from scanf\n"); return (1); /* or exit (1); */ } if ( rcode == EOF ) break; sum += cur_value; } /* the break comes here */ printf ("The sum is %d\n", sum); return (0); }These routines have much more complicated parameter processing - scanf generates a return code, and also retrieves an actual integer value. To understand parameter passing in functions we must understand pointers and how memory is referenced in C.
Pointers and Addresses
C has a simple memory model. Blocks of memory are organized as a sequence of bytes which can be manipulated individually or in contiguous groups. Each byte of memory has an address. The basic unit of storage is the 8-bit byte, and it is usually safe to assume that a char is exactly one byte.
An address is stored in a pointer.
This is an
unsigned
int.
Each datatype requires one or more bytes to store its value. Typically a character requires one byte (for the moment, but there are cases when you need more, for instance Japanese and Chinese Characters), an integer usually uses between 2 and 8 bytes, and so on. Thus not every byte address is a legitimate address of a data object (and this is true in all computers).
An example:
A pointer, p,
to an integer data object, a,
whose value is 19 is declared as follows:
int a = 19; int* p = &a;
An array name is really a pointer, it points to the first element of the array
For example:
int a[100]; int* pa;
pa = a;
Both pa and a point to the first element of the array of 100 integers. Thus both the following reference the same array element (the i'th element):
The expression pa+i takes the value of the pointer pa and adds i elements to it. Thus pa+i points to the i'th element of the array a.
The * operator treats its operand as a pointer and retrieves the value at that address, thus *(pa+i) first computes the address of element i in array a, and then retrieves its value
The & operator
is used to compute the
address of a
variable, for example
pa = &a[2];stores the address of the 2'nd element of a in pa
But the expression
*(pa+2)will now retrieve the value of a[4]
There is an important distinction between array and pointer declarations.
int* pa[10];
This produces an array containing 10 pointers to integers
The C and Unix Memory Model
Memory is a sequence of bytes, each byte with a specific address. The general large scale organization of memory for a C program running under Unix is as follows:
small addresses
large addresses