-
Consider the following program, which uses
getchar()
and
putchar()
to copy standard input to output a byte at a time. Start by reading the man
page for getchar()
and putchar
. That's
man -s 3 getchar
since the C library functions are in section 3 of the manual.
Here's the program. It should look familiar, as it's on page 17 of K&R.
/* copies standard input to standard output a byte at a time */
#include <stdio.h>
int main(void)
{
int c;
while((c = getchar()) != EOF)
putchar(c);
return 0;
}
Make a copy of this program and call it q1.c. Compile (to q1) and test it on
some files you create. In the following example, the input to q1 is redirected
to come from a file
called "infile" and the output is redirected to go to a file called "outfile".
If q1.c works correctly, the two files should be byte-for-byte identical.
We test this using the diff
command. (Of course, you'll now hurry
off and look at the man page for diff
.) diff
returns
silently if the two files are identical, and complains if they are not.
$
$ cat infile
Here is a testfile
with some
stuff in it.
$
q1 < infile > outfile
$ diff infile outfile
$
Change one byte in one of the files and call diff
again to see
what happens.
You can see the bytes (expressed as 3 octal digits) in a file by using the
command od -cb filename
. od
stands for "octal dump"
(... man page ... ).
$
$ od -cb infile
0000000 H e r e i s a t e s t f i
110 145 162 145 040 151 163 040 141 040 164 145 163 164 146 151
0000020 l e \n w i t h s o m e \n s t u
154 145 012 167 151 164 150 040 163 157 155 145 012 163 164 165
0000040 f f i n i t . \n
146 146 040 151 156 040 151 164 056 012
0000052
$
-
Copy program q1.c to q1a.c and change q1a.c so that in the output it replaces
all small letters with the corresponding capitals and leaves an additional
empty line between each of the existing lines, like this:
HERE IS A TESTFILE
WITH SOME
STUFF IN IT.
Don't worry if you add an extra blank line after the last line.
See the man page for toupper
.
See appendix B2 in the text.
-
Make a copy of q1.c called q1b.c. Change it so that the variable
c
is declared as unsigned char c;
. Show that the new
program is no longer guaranteed to correctly copy input to output.
Note: To kill a running program, type in ctrl-c
-
Make a copy of q1.c called q1c.c. Change it so that the variable
c
is declared as signed char c;
. Show that the new
program is no longer guaranteed to correctly copy input to output. This is a
bit more difficult and you may want to skip this for now and try it at home.
-
Write a C program called q2.c that uses
gets
and puts
to copy standard input to standard output a line at a time. Assume that the
lines in a file are never longer than 20 characters, including the newline
character at the end. First, of course, see
gets
and puts
in the text and on the man pages.
-
Write a program called q3.c that
demonstrates why you should avoid using
gets
.
Start by making a copy of q2.c. Add declarations for 2 ints, one
before the declaration of the char array, and one after, as in:
int n1 = 0xb76f81c4;
char buf[9];
int n2 = 0x81ef3ba2;
You can print out the addresses where n1, buf and n2 are stored like this:
printf(" n1 starts at %p\n", &n1);
printf("buf starts at %p\n", buf);
printf(" n2 starts at %p\n\n", &n2);
Then, after the while loop used to read and write lines
using gets and prints, add 2 printf statements to print out the values
of n1 and n2.
Try running this program when the longest input line, counting the newline
character, is 9 bytes, then 10, 11, 12, 13, 14. Can you explain what happens?
Here is a description of what is going on with q3:
memory_layout
-
Write a C program called q4.c that uses
fgets
and
fputs
to copy standard input to standard output a line at a time.
Use a buffer size of 9, but experiment with longer lines in a file. First, of
course, see fgets
and fputs
in the text and in the
man pages. The simplest thing to do is to make a copy of q2.c and replace
the calls to gets and puts with calls to fgets and fputs. Compare the
output to the input file using diff. Then change q4.c to be a copy of q3.c
and change calls to gets and puts to calls to fgets and fputs. Are the
results the same or different when you run q4 with input from the file used
to test q3?
When you get this working, forget all about gets
and
puts
. Never use them again. Only use fgets
and
fputs
.
-
Write a C program called q5.c. It should read ints from standard input until
a non-int or end-of-file is encountered. It should echo each int alone on a
line and finally output the total, as in the following two examples.
BUT FIRST think about what kind of input to use: character oriented
(getchar), token oriented (scanf) or line oriented (fgets).
If you use character oriented, then you need to ignore characters that aren't
digits and somehow build up the integers as you read - no fun. If you use
line oriented input, you'll get the line, stored in a char array, and then
have to extract the integers from that - easier than getchar, but still a lot
of trouble. Clearly,
scanf is the way to go here - it skips over whitespace and grabs
the next token as an int (or whatever you tell it).
So - given a programming task, always think about what is the right way to deal with I/O. Not doing that is one of the most common beginner
mistakes. And now on to the q5 example runs.
$
$ cat numbers
4 6
3 2 1
10
$ q5 < numbers
4
6
3
2
1
10
total = 26
$
$
$ cat numbers2
4 6
3 2 1
10Hello
500 1000
$
$ q5 < numbers2
4
6
3
2
1
10
total = 26
$
-
Write a C program called q6.c. It should behave like q5.c, except that it should skip over
everything that's not in 0 ... 9 and just pick out the ints. The output should be like that
for q5.c. See "scanf assignment suppression" and "[^ ... ]" in scanf conversion.
Here is an example:
$
$ cat numbers3
%&4**XY6
3~~=VR)[2*n1
a*(b
10Hello
$
$ q6 < numbers3
4
6
3
2
1
10
total = 26
$