You can obtain the skeleton code and solutions for this recitation on CLAC:
git clone ~j-hui/cs3157-pub/examples/cats
decho
and dcat
In this exercise, we will use two programs, decho
and dcat
, to experiment
with FILE I/O functions provided by the C standard library.
decho
takes decimal integers as command-line arguments, and writes those
decimal values as individual bytes to stdout:
// decho.c
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
int main(int argc, char **argv) {
for (int i = 1; i < argc; i++) {
unsigned char d = atoi(argv[i]);
fwrite(&d, 1, 1, stdout);
}
}
dcat
receives bytes from its stdin, and prints them as comma-separated
decimal integers to its stdout:
// dcat.c
#include <stdio.h>
int main(void) {
unsigned char d;
while (fread(&d, 1, 1, stdin))
printf("%d, ", d);
printf("\n");
}
We can use these programs together to construct and inspect file streams at a byte-by-byte level:
$ gcc -o decho decho.c && gcc -o dcat dcat.c
$ ./decho 0 1 2 5 10 | ./dcat
0, 1, 2, 5, 10,
Consider the following program, named cats
:
// cats.c: cats play with strings!
#include <stdio.h>
int main(void) {
char buf[8];
fgets(buf, 8, stdin);
fputs(buf, stdout);
return 0;
}
We build and run it as follows:
$ gcc -o cats cats.c
$ ./decho 1 2 3 4 5 6 7 8 | ./cats | ./dcat
(1.1)
$ ./decho 9 10 11 12 13 14 15 16 | ./cats | ./dcat
(1.2)
$ ./decho 1 1 0 1 0 | ./cats | ./dcat
(1.3)
What is the output shown at 1.1–1.3?
Now, consider the following program, named jellicle
:
// jellicle.c: like cats, except nobody asked for this.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <arpa/inet.h>
int main(int argc, char **argv) {
assert(argc == 2 && atoi("10") == '\n' && '-' == 45 &&
'0' == 48 && '1' == 49 && '2' == 50 && '3' == 51 && '4' == 52 &&
'5' == 53 && '6' == 54 && '7' == 55 && '8' == 56 && '9' == 57);
char buf[8];
uint32_t xs[2];
if (argv[1][0] == '4') {
fgets((char *)xs, sizeof(xs), stdin);
if (xs[0])
xs[0] >>= xs[1];
else
xs[0] = ntohl(xs[1]);
printf("%d\n", *xs);
} else if (argv[1][0] == '3') {
char *s = buf + 4;
while (fread(--s, 1, 1, stdin) > 0)
;
printf("%s\n", buf);
} else if (argv[1][0] == '2') {
while (fread(buf, 1, 4, stdin) > 0)
fputs(buf, stdout);
} else if (argv[1][0] == '1') {
argv[1][0] = '4';
argv[1] = buf;
argv[0] = "./decho";
fgets(buf, 3, stdin);
execv(argv[0], argv);
}
}
We build and run it as follows:
$ gcc -o jellicle jellicle.c
$ ./decho 0 0 0 0 0 0 0 0 | ./jellicle 4 | ./dcat
(2.1)
$ ./decho 0 0 0 0 0 0 1 1 | ./jellicle 4 | ./dcat
(2.2)
$ ./decho 2 0 0 0 0 0 0 0 | ./jellicle 4 | ./dcat
(2.3)
$ ./decho 0 2 0 0 2 0 0 0 | ./jellicle 4 | ./dcat
(2.4)
$ ./decho 0 1 2 3 | ./jellicle 3 | ./dcat
(2.5)
$ ./decho 4 5 6 7 | ./jellicle 3 | ./dcat
(2.6)
$ ./decho 1 0 1 | ./jellicle 3 | ./dcat
(2.7)
$ ./decho 4 2 0 0 2 4 | ./jellicle 2 | ./dcat
(2.8)
$ ./decho 4 2 10 10 2 4 | ./jellicle 2 | ./dcat
(2.9)
$ ./decho 55 55 55 55 55 | ./jellicle 1 | ./dcat
(2.10)
What is the outcome of running each command? For 2.1–2.10, if there is a memory error, write ERROR; otherwise, if the output is unpredictable, write UNPREDICTABLE; otherwise, write the output (including commas).