fgets()
function (K&R § 7.7)
The fgets()
function can be used to read input a
full line at a time. This function takes three arguments:
a pointer to the first character of an allocated buffer (the buffer
could either be an array or it could have been allocated dynamically), a
maximum buffer size (say, maxline
), and a FILE *
(stdin
, representing standard input, is an appropriate
value for this parameter).
fgets()
will read the next line (including the newline)
from the specified file and store them in the specified buffer. However,
it will never read/store more than maxline-1
characters.
(This makes it possible to safely read lines from a file without fear
of overflowing a buffer.) Because of this, fgets()
will
only read/store the first maxline-1
characters on a line.
If there are any remaining characters on the line (including the newline),
they will be read on subsequent input requests. If a line has been
fully read, then the last character stored in the buffer should be
a \n
(unless, of course, there was no newline in the
input).
Note that fgets()
will automatically put a nul byte in
the buffer after the last character it reads. fgets()
returns NULL in the event of error or end-of-file.
The following program will read input from standard input and echo each line (enclosed in double quotes) to the screen:
#include <stdio.h> #include <string.h> #define BUFFER_LEN (80+1+1) /* 80 regular characters * +1 for the newline * +1 for nul byte */ int main() { char buffer[BUFFER_LEN]; while (fgets(buffer, sizeof(buffer), stdin) != NULL) { /* Get rid of the newline */ if (buffer[strlen(buffer)-1] == '\n') buffer[strlen(buffer)-1] = '\0'; printf("Read string: \"%s\"\n", buffer); } return 0; }
As mentioned earlier, we can pass pointers to variables in order to simulate pass by reference. We already saw an example of this with arrays. Instead of passing the entire array, a pointer to the first element of the array is implicitly passed instead. Any changes to the array contents inside the function will affect the original array as well.
In order to explicitly pass pointers to variables of other types
(e.g. integers), we must use the address-of operator,
&
, as described earlier. For example, consider the
following two functions, each of which, on the surface appear to do the
same thing:
#include <stdio.h> /* no_swap()'s parameters are of type "integer" */ void no_swap(int a, int b) { int tmp = a; a = b; b = tmp; printf("At end of no_swap(): a = %d, b = %d\n", a, b); } /* swap()'s parameters are of type "pointer to integer" */ void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; printf("At end of swap(): *a = %d,*b = %d\n", *a, *b); } int main() { int a = 1, b = 2; printf("Originally: a = %d, b = %d\n", a, b); /* Pass integers */ no_swap(a, b); printf("After return from no_swap() a = %d, b = %d\n", a, b); /* Pass pointers to integers */ swap(&a, &b); printf("After return from swap() a = %d, b = %d\n", a, b); return 0; }
There is a very important, but subtle difference between the two
functions, however. no_swap()
is passed copies of the
values of the variables a
and b
.
The function then exchanges these two copies while leaving the original
values of a
and b
in the main()
function unchanged. The fact that a
and b
are changed inside no_swap()
, but are unchanged outside is
demonstrated by the output generated by the calls to printf()
.
The function swap()
on the other hand, is passed copies
of the addresses of a
and b
.
Because swap()
now knows the addresses of the values of
these variables, it can make changes to the variables' values themselves
and have these changes reflected in the in context of the calling
function (main()
, in this case).
Note that swap()
is still receiving its parameters by
value. However, instead of passing it copies of the values
of a
and b
, it is being passed copies of
their respective addresses. By modifying the values at
these addresses, swap
is able to exchange the values of
a
and b
and have those changes preserved when
the function ends.
char string[]
vs char *string
(K&R § 5.3)
As a convention, when passing pointers, it is common to always use the
*
symbol in the parameter declaration, even if what is
actually being passed is an array. For example, previously, we saw the
alternate_caps()
function declared as
int alternate_caps(char string[]);
In practice, however, the []
notation is not frequently used
when we are passing one dimensional arrays. Instead, it is generally
agreed that because an array passed as a parameter degrades into a pointer
to the array's first element, it is more accurate to declare/define the
function as:
int alternate_caps(char *string);
Both declarations are essentially equivalent, but do not jump to the conclusion that arrays and pointers are the same thing -- they are different beasts entirely. The fact that arrays become pointers to their first elements does not mean that pointers and arrays can be used interchangeable.
A similar strategy can be used when passing arrays with more than one dimension. However, remember that the array degrades into a pointer to its first element. The first element of a two dimensional array is a one dimensional array (i.e. the first row of the array). So when we pass a two dimensional array as a parameter, what the function receives is a pointer to the first element of the array, which, in this case, is an array representing the first row. Therefore, the function gets a pointer to an array.
Using the border()
function from Assignment #1 as an example,
the way to represent a pointer to an array would be:
int border(char (*ar)[MAX_COL], int nrow, int ncol)
This declares ar
to be a pointer to an array of size
MAX_COL
. Note that because of the associativity of the
[]
and *
operators, we could not write:
int border(char *ar[MAX_COL], int nrow, int ncol)
because that would declare ar
to be an array of pointers,
which is something completely different than a pointer to an array
(K&R § 5.7, 5.9).
In all our examples, so far, we have had arrays of fixed size. C allows us to request dynamically allocated memory, the size of which may not be known until runtime. For example, consider the following program which dynamically allocates, uses, then deallocates an array of integers.
#include <stdio.h> #include <stdlib.h> void initialize(int *mem, int num_elements, int init); void show(int *mem, int num_elements); int main() { int num, init; int *mem; printf("How big an array would you like? "); scanf("%d", &num); if ((mem = (int *) malloc(sizeof(int) * num)) == NULL) { fprintf(stderr, "Unable to allocate memory\n"); exit(1); } printf("What would you like the initial value to be? "); scanf("%d", &init); initialize(mem, num, init); show(mem, num); free(mem); return 0; } void initialize(int *mem, int num_elements, int init) { int i; for (i = 0; i < num_elements; i++) *(mem + i) = init++; } void show(int *mem, int num_elements) { int i; puts("The values in the array are: "); for (i = 0; i < num_elements; i++) printf("mem[%d] = %d\n", i, mem[i]); }
scanf()
function (K&R § 7.4)
The program first asks the user how large an array to allocate,
then it acquires user input using the scanf()
function.
The scanf()
function takes a format string argument
containing conversion specifiers (the format string is similar
to printf()
) and a list of variable addresses. The
scanf()
function will then read, from standard input,
the values types specified by the format string and store them in the
locations given by the rest of the argument list.
We can also use scanf
to input strings by using a
%s
conversion specifier and a corresponding character
array in the argument list. Note that because an array becomes
a pointer to the first element of the array, there should be
no &
preceding the array name:
char str[10]; scanf("%s", str);
Generally speaking, this method of inputting strings is very unsafe,
because a user could always enter more characters that have been
allocated by the array and scanf()
will write character
outside the array bounds. A much safer way of doing string input is
with fgets()
, which we will study later.
fprintf()
function (K&R § 7.5)
When an error occurs in your program (for example, a call to
malloc()
fails), it is usually a good idea to print
diagnostic information describing the error to a different output
stream than your regular output. This way, diagnostic output (such as
warnings/error produced by your program) can be viewed separately from
your program's regular output.
This can be done by using the fprintf()
function.
fprintf()
operates the same as printf()
except
that it has an additional parameter, namely the file to which to write
the formatted string. By default, three "files" are accessible by C: standard input (stdin
), standard output
(stdout
) and standard error (stderr
). By default,
standard input is your keyboard; standard error and standard output are
both displayed on your console. Using redirection, we can change this:
$ ./a.out < input.txt > output.dat 2> error.log
This invocation causes a.out
to read input from
the file named input.txt
, sends output generated
by printf()
, puts()
etc. to the
file output.dat
and sends output generated by
fprintf(stderr, ...)
to the file error.log
The 2
in front of the '>
' redirection symbol
represents the stderr
output file.
Note that printf(...)
is identical to fprintf(stdout,
...)
. Also, in general fprintf()
can be used to
write data to files opened explicitly by your C
programs.
malloc()
and free()
functions
(K&R § 6.5, 7.8.5)
Once the program obtains the size of the array, the malloc()
function is called. (This function is declared in stdlib.h
,
so we must #include
that file before using the function.)
The malloc()
function, takes, as its argument, the
number of bytes to allocate. In our example, we are allocating an
area of memory to store integers, therefore, each element of the
array will require sizeof(int)
bytes. In order to store
num
integers, we will require sizeof(int)*num
bytes altogether, hence explaining the argument to malloc()
.
The malloc()
function returns a pointer to the
first address of the region of memory that it allocated for us.
As with uninitialized arrays, the contents of the memory will be
uninitialized. If malloc
was unable to satisfy our
request for memory a NULL
pointer will be returned.
Because malloc()
is not aware of the type of data which
is being allocated, the pointer returned from malloc()
is
void
(i.e. malloc
returns a void
*
), This basically means that malloc()
returns a
pointer to an unknown type. Before we can assign this void *
to mem
(which is defined as an int*
), we
must force the return value from malloc()
to be an int
*
. We do this by using a typecast: We prefix the the
call to malloc()
with the typecast: (int *)
(the parenthesis are required.)
Once we have the pointer returned by malloc()
stored in
mem
, we can use mem
as if it were an array,
as demonstrated by the initialize()
and show()
functions, which, respectively assign sequentially increasing numbers to
each subsequent array element (starting from a initial number specified
by the user) and output the values in the dynamically allocated array.
Note, that like an array, we can either use the *(mem + i)
notation for accessing elements of this array or the more conventional
mem[i]
notation. mem
is a pointer to the
first element of the array.
When we are finished with mem
, we must remember to deallocate
it by calling free()
. Note that if we do not call free
, then memory allocated will be retained by the program until the program
terminates, after which the memory will be reclaimed by the operating
system. In our particular example, free()
'ing the memory
isn't particularly important, since the program finishes immediately
after we do. However in programs that are to be run for extended periods
of time without stopping, forgetting to deallocate dynamically allocated
memory can lead to a memory leak which can slowly drain all the computer's
memory resources.
malloc()
and free()
There are three important things to remember when dealing with dynamically allocated:
malloc()
returns NULL
).
calloc()
function
There is another function, called calloc()
which operates
similarly to malloc()
except that it takes two parameters
instead of one. The first parameter represents the number of bytes
of each array element and the second number represents the total number
of elements to allocate. (calloc()
also has the added
feature that the allocated chuck of memory is set to zero.) The above
call to malloc()
could therefore be replaced with the following
(note that the typecast is still required):
if ((mem = (int *) calloc(sizeof(int), num)) == NULL)
...
The above code can be made more modular by introducing a
allocate()
function to do our memory allocation for
us instead of allocating it directly inside main()
:
#include <stdio.h> #include <stdlib.h> void allocate(int **mem, int num_elements); void initialize(int *mem, int num_elements, int init); void show(int *mem, int num_elements); int main() { int num, init; int *mem; printf("How big an array would you like? "); scanf("%d", &num); printf("What would you like the initial value to be? "); scanf("%d", &init); allocate(&mem, num); initialize(mem, num, init); show(mem, num); free(mem); return 0; } void allocate(int **mem, int num_elements) { if ((*mem = (int *) malloc(sizeof(int) * num_elements)) == NULL) { fprintf(stderr, "Unable to allocate memory\n"); exit(1); } } void initialize(int *mem, int num_elements, int init) { int i; for (i = 0; i < num_elements; i++) *(mem + i) = init++; } void show(int *mem, int num_elements) { int i; puts("The values in the array are: "); for (i = 0; i < num_elements; i++) printf("mem[%d] = %d\n", i, mem[i]); }
Note the difference between the two programs:
A new function allocate()
has been added. Note that
this function takes a pointer to a pointer to int
as the
first argument (int **
). Because allocate()
modifies the mem
parameter and because we want this change to
be reflected in the calling function (i.e. main()
),
the main()
function must pass in the address
of mem
, hence giving rise to the double pointer. Note that
inside allocate()
we dereference mem
when
assigning to it. By doing so, we simultaneously update *mem
in allocate()
and mem
in main()
.
If we had defined allocate()
as follows:
void allocate(int *mem, int num_elements) { if ((mem = (int *) malloc(sizeof(int) * num_elements)) == NULL) { fprintf(stderr, "Unable to allocate memory\n"); exit(1); } }
and main()
had passed in mem
(and not
&mem
), then allocate()
's copy of
mem
would be assigned a pointer to the newly allocated
memory, but the mem
pointer in main()
would
still be uninitialized. When allocate()
is finished,
the memory that it dynamically acquired would still be allocated, but
there would no longer be any way to refer to it -- the memory will have
been leaked.
An alternative way of defining the allocate()
function is to
have it return a pointer to the allocated memory. For example:
int *allocate(int num_elements) { int *mem; if ((mem = (int *) malloc(sizeof(int) * num_elements)) == NULL) { fprintf(stderr, "Unable to allocate memory\n"); exit(1); } return mem; }
Now, when we call allocate()
, we must assign the pointer
returned from the function to mem
in the main()
function (of course, we must still remember to free()
it as well):
mem = allocate(num);
Last modified: Tue Jan 28 22:41:40 2003