5
\$\begingroup\$

I have written a reimplementation of cat that will be compliant with most scripts, and that is as small and fast as possible. Can anybody suggest some size improvements for it?

#include <stdio.h> /* we need printf, fprintf, FILE,
 * fopen, fclose and that sort of
 * stuff */
#include <stdlib.h> /* for exit, EXIT_SUCCESS, and
 * EXIT_FAILURE */
int main(int argc, char *argv[])
{
 FILE *fp; /* file to cat */
 short int ch; /* character to print */
 unsigned short int cp; /* currently printing argv[?] */
 unsigned short int failed = 0; /* how many failed to print */
 if (argc < 2) { /* if not enough args act as pipe */
 for (;;) { /* infinite loop */
 if ((ch = fgetc(stdin)) == EOF) {
 exit(EXIT_SUCCESS);
 } else {
 printf("%c", ch);
 }
 }
 }
 for (cp = 1; cp <= argc - 1; cp++) { /* for each file in cli args */
 fp = fopen(argv[cp], "r"); /* open the file */
 if(fp == NULL) {
 fprintf(stderr, "%s: cannot open %s\n", argv[0], argv[cp]);
 failed++; /* increment the number of failed */
 continue; /* go back to beginning */
 }
 while ((ch = fgetc(fp)) != EOF) {
 printf("%c", ch);
 }
 fclose(fp); /* close the file */
 }
 exit(failed); /* exit with the number of files */
} /* that failed */

I'm sorry that the indentation is messed up, I use tabs in my code.

In clang version 3.5.0 on Ubuntu the generated binary size is 5200 bytes with these options:

-Oz -fomit-frame-pointer -s

Improvements

I added some more error checking, added -u and - and also wrote a function that writes out a file to avoid repeating myself. Also changed short int to int, printf to putchar, and some other things.

#include <stdio.h> /* we need printf, fprintf, FILE,
 * fopen, fclose and that sort of
 * stuff */
#include <stdlib.h> /* for exit, EXIT_SUCCESS, and
 * EXIT_FAILURE */
#include <string.h> /* need strcmp */
void write_file(char *file, char *progname) /* function to print individual file */
{
 FILE *fp; /* file to write */
 int ch; /* temporary variable */
 if (strcmp(file, "-") == 0) { /* check if using stdin */
 fp = stdin; /* use standard input */
 } else {
 fp = fopen(file, "r"); /* open file for writing */
 }
 if (fp == NULL) { /* error in opening file */
 fprintf(stderr, "%s: cannot open %s\n", progname, file);
 exit(EXIT_FAILURE);
 }
 while ((ch = fgetc(fp)) != EOF) {
 if (putchar(ch) == EOF) {
 fprintf(stderr, "%s: can't read %s\n", progname, file);
 exit(EXIT_FAILURE);
 }
 }
 if (ferror(fp) != 0) { /* IO error, perhaps? */
 fprintf(stderr, "%s: error reading %s\n", progname, file);
 exit(EXIT_FAILURE);
 }
 if (strcmp(file, "-") != 0) { /* close the file unless stdin */
 fclose(fp);
 }
 return;
}
int main(int argc, char *argv[])
{
 unsigned short int cp = 1; /* currently printing argv[?] */
 if (argc < 2) { /* act as pipe */
 write_file("-", argv[0]);
 exit(EXIT_SUCCESS);
 }
 if (strcmp(argv[1], "-u") == 0) { /* support -u option for SUS compat */
 cp++; /* increment the argv pointer var */
 }
 for (; cp <= argc - 1; cp++) { /* for each file in cli args */
 write_file(argv[cp], argv[0]); /* print the file */
 }
 exit(EXIT_SUCCESS);
}
t3chb0t
44.7k9 gold badges84 silver badges190 bronze badges
asked May 10, 2015 at 11:44
\$\endgroup\$
6
  • 3
    \$\begingroup\$ "most scripts" - it already doesn't handle the - and -u arguments; you could start by reading the POSIX spec. "fast and small", does that mean with or without linked libraries? \$\endgroup\$ Commented May 10, 2015 at 12:09
  • 1
    \$\begingroup\$ You've got a bug if 256 files are passed as arguments and they all fail. Handling - would be pretty necessary though. Are you stuck with an ancient compiler? \$\endgroup\$ Commented May 10, 2015 at 16:45
  • \$\begingroup\$ Don't use tabs, You are not trying to optimize the byte size of your source code ;-) \$\endgroup\$ Commented May 10, 2015 at 19:44
  • 2
    \$\begingroup\$ printf("%c", ch); -> fputc(ch, fp); or putchar(ch); \$\endgroup\$ Commented May 10, 2015 at 19:45
  • 3
    \$\begingroup\$ short int ch; shoud be int ch; EOF is not guaranteed to fit in a short \$\endgroup\$ Commented May 10, 2015 at 19:46

2 Answers 2

3
\$\begingroup\$
  • Do not repeat yourself. Copying stdin doesn't differ from copying any other file. There is no need to special case stdin as you do at lines 15-21.

  • Avoid naked loops. Every loop does an important job that deserves a name. This is strong enough reason to factor it out as a function:

  • Expect errors

    • fgetc may fail. Once fgetc returns EOF, call feof or ferror to see what has actually happened.

    • printf may fail. Make sure to detect (printf returns -1) and report (analyze errno) errors.

  • Handle options. At least recognize a standalone - as a fake file name referring to stdin.

answered May 10, 2015 at 16:55
\$\endgroup\$
3
\$\begingroup\$

May be small, but isn't fast

Your program suffers from operating on a one character basis. There is just too much overhead when reading and writing single characters, even if your file I/O is fully buffered.

I ran your program on a 100 MB file. It took 30 seconds. Meanwhile, the normal cat program finished in 0.07 seconds.

I modified your program to use fread into a 32KB buffer and then fwrite the buffer to stdout. This make your program run in 0.07 seconds.

Text mode

Actually I would avoid use FILE functions and just use open, read, and write. The reason is that stdout defaults to being open in text mode. On a Linux system that wouldn't matter, but on a Windows system that could lead to the FILE functions doing strange things with the newline characters. There isn't a portable way of changing the mode of stdout to binary either.

answered May 11, 2015 at 7:06
\$\endgroup\$
1
  • \$\begingroup\$ It's funny that you think using a buffer increases memory usage when using the FILE functions will already create a hidden buffer for you. Anyways, you don't have to make the buffer 32K - it can be as small as you want it to be. As far as the portability of open vs fopen, you're right that fopen is more portable. Personally I've never seen a system where there wasn't open but maybe you know of an embedded system like that. \$\endgroup\$ Commented May 11, 2015 at 14:42

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.