Computer Systems Organization


Start Lecture #11

5.10: Command-line Arguments

cmdline

On the right is a picture of how arguments are passed to a (Unix) command. Each main() program receives two arguments an integer, normally called argc for argument count, and an array of character pointers, normally called argv for argument vector.

The diagram shows argv as an array and the code below treats it that way as well. As always, an array name is also a pointer to the first element. If you view argv as a pointer, then you would draw a box for it with an arrow pointing to the array. The book pictures it that way.

#include <stdio.h>
int main(int argc, char *argv[argc]) {
  int i;
  printf("My name is %s.\n", argv[0]);
  printf("I was called with %d argument%s\n",
         argc-1, (argc==2) ? "" : "s");
  for (i=1; i<argc; i++)
    printf("Argument #%d is %s.\n", i, argv[i]);
 }

sh-4.0$ cc -o cmdline cmdline.c sh-4.0$ ./cmdline My name is ./cmdline. I was called with 0 arguments. sh-4.0$ ./cmdline x My name is ./cmdline. I was called with 1 argument. Argument #1 is x. sh-4.0$ ./cmdline xx y My name is ./cmdline. I was called with 2 arguments. Argument #1 is xx. Argument #2 is y. sh-4.0$ ./cmdline -o cmdline cmdline.c My name is ./cmdline. I was called with 3 arguments. Argument #1 is -o. Argument #2 is cmdline. Argument #3 is cmdline.c. sh-4.0$ cp cmdline mary-joe sh-4.0$ ./mary-joe -o cmdline cmdline.c My name is ./mary-joe. I was called with 3 arguments. Argument #1 is -o. Argument #2 is cmdline. Argument #3 is cmdline.c.

Since the same program can have multiple names (more on that later), argv[0], the first element of the argument vector, is a pointer to a character string containing the name by which the command was invoked. Subsequent elements of argv point to character strings containing the arguments given to the command. Finally, there is a NULL pointer to indicate the end of the pointer array.

The integer argc gives the total number of (valid) pointers, including the pointer to the name of the command. Thus, the smallest possible value for argc is 1 and argc is 3 for the picture drawn above.

The code on the right shows how a program can access its name and any arguments it was called with.

Having both a count (argc) and a trailing NULL pointer (argv[argc]==NULL) is redundant, but convenient. The code I wrote treats argv as an array. It loops through the array using the count as an upper bound. Another style would use something like

    while (*argv)  printf("%s\n", (*argv++));
  
which treats argv as a pointer and terminates when argv points to NULL.

The second frame on the right shows a session using the code directly above it.

  1. First, we show how to compile a C program and not have the result called a.out.
  2. Next we run the resulting program with differing numbers of arguments. Note the use of the conditional expression to get singular and plural correct.
  3. Finally, we show one way (via copying the executable) that the same program can have more than one name. We could have renamed/moved the executable as well. Another way would have been to recompile the program giving a different file name after -o (or not using -o and getting a.out). In 202 you will learn two other ways (hard and soft links).

Simple Application of Command Line Arguments

Now we can get rid of some symbolic constants that should be specified at run time.

Here are some before and after examples. The code on the left uses symbolic constants; on the right we use command line arguments

Fahrenheight to Celcius

#include <stdio.h>
#define LO 0
#define HI 300                  #include <stdio.h>
#define INCR 20                 #include <stdlib.h>
main() {                        int main (int argc, char *argv[argc]) {
  int F;                          int F;
  for (F=LO; F<=HI; F+=INCR)      for (F=atoi(argv[1]); F<=atoi(argv[2]); F+=atoi(argv[3]))
    printf("%3d\t%5.1f\n", F,       printf("%3d\t%5.1f\n", F,
           (F-32)*(5.0/9.0));              (F-32)*(5.0/9.0));
}                                 return 0;
                                }
  1. Note that now main() is specified correctly; it returns an integer and has the complicated argument structure we just described. As written on the left the program terminates abnormally (it doesn't return 0).
  2. Note the use of atoi() to convert the ascii (character) form of the numerical inputs into integers.

Solving Quadratic Equations

#include <stdio.h>
#include <math.h>
#define A +1.0   // should read                #include <stdio.h>
#define B -3.0   // A,B,C                      #include <math.h>
#define C +2.0   // using scanf()              #include <stdlib.h>
void solve (float a, float b, float c);        void solve (float a, float b, float c);
int main() {                                   int main(int argc, char *argv[argc]) {
  solve(A,B,C);                                  solve(atof(argv[1]), atof(argv[2]),
                                                       atof(argv[3]));
  return 0;                                      return 0;
}                                              }
void solve (float a, float b, float c) {       void solve (float a, float b, float c) {
  float d;                                       float d;
  d = b*b - 4*a*c;                               d = b*b - 4*a*c;
  if (d < 0)                                     if (d < 0)
    printf("No real roots\n");                     printf("No real roots\n");
  else if (d == 0)                               else if (d == 0)
    printf("Double root is %f\n", -b/(2*a));       printf("Double root is %f\n", -b/(2*a));
  else                                           else
    printf("Roots are %f and %f\n",                printf("Roots are %f and %f\n",
           ((-b)+sqrt(d))/(2*a),                          ((-b)+sqrt(d))/(2*a),
           ((-b)-sqrt(d))/(2*a));                         ((-b)-sqrt(d))/(2*a));
}                                              }
  1. Again main() now specified correctly. When we had main() we said don't check the arguments. Now we specify them correctly.
  2. This time we need atof() since the arguments are floating point.
#include <stdio.h>
#include <ctype.h>
int main (int argc, char *argv[argc]) {
  int c, makeUpper=0;
  if (argc > 2)
    return argc;
  if (argc == 2)
    if (strcmp(argv[1], "-toupper")){
      printf("Argument %s is illegal.\n", argv[1]);
      return -1;
    }
    else
      makeUpper=1;
  while ((c = getchar()) != EOF)
    if (!isdigit(c)) {
      if (isalpha(c) && makeUpper)
	c = toupper(c);
      putchar(c);
    }
  return 0;
}

Specifying Options

Often a leading minus sign (-) is used for optional command line arguments. The program on the right removes all digits from the input. If it is given the argument -toupper it also converts all letters to upper case using the toupper() library routine.

Demo this function on ajglap.cs.nyu.edu.

Homework: Combine the entab and detab functions by writing a function tab that has one argument

    tab -en   # performs like entab
    tab -DE   # performs like detab
  
Use a reasonable default for tab width. No need to support the user specifying the tab width, i.e., always use the default value.

5.11: Pointers to Functions

#include <ctype.h>
#include <string.h>
#include <stdio.h>
// Simple program to illustrate function pointers
int digitToStar(int c);   // Convert all digits to *
int letterToStar(int c);  // Convert all letters to *
int main (int argc, char *argv[argc]) {
  int c;
  int (*funptr)(int c);
  if (argc != 2)
    return argc;
  if (strcmp(argv[1],"digits")==0)
    funptr = &digitToStar;
  else if (strcmp(argv[1],"letters")==0)
    funptr = &letterToStar;
  else
    return -1;
  while ((c=getchar())!=EOF)
    putchar((*funptr)(c));
  return 0;
}
int digitToStar(int c) {
  if (isdigit(c))
    return '*';
  return c;
}
int letterToStar(int c) {
  if (isalpha(c))
    return '*';
  return c;
}

In C you can do very little with functions, mostly define them and call them (and take their address, see what follow).

However, pointers to functions (called function pointers) are real values.

  1. A function can return a function pointer.
  2. You can declare variables that hold function pointers.
  3. You can have an array of function pointers.
  4. You can have a structure with function pointer components.
  5. A function can take a function pointer argument.
  6. etc

The program on the right is a simple demonstration of function pointers. Two very simple functions are defined.

The first function, digitToStar() accepts an integer (representing a character) and return an integer. If the argument is a digit, the value returned is (the integer version of) '*'. Otherwise the value returned is just the unchanged value of the argument.

Similarly letterToStar() convert a letter to '*' and leaves all other characters unchanged.

The star of the show is funptr. Read its declaration carefully: The variable funptr is the kind of thing that, once de-referenced, is the kind of thing that, once given an integer, is an integer.

So it is a pointer to something. That something is a function from integers to integers.

The main program checks the (mandatory) argument. If the argument is "digits", funptr is set to the address of digitToStar(). If the argument is "letters", funptr is set to the address of letterToStar().

Then we have a standard getchar()/putchar() loop with a slight twist. The character (I know it is an integer) sent to putchar() is not the naked input character, but instead is the input character processed by whatever function funptr points to. Note the "*" in the call to putchar().

Note: C permits abbreviating &function-name to function-name. So in the program above we could say

    funptr = digitToStar;
    funptr = letterToStar;
  
instead of
    funptr = &digitToStar;
    funptr = &letterToStar;
  
I don't like that abbreviation so I don't use it. Others do like it and you may use it if you wish.

One difference between a function pointer and a function is their size. A big function is big, a small function is small, and an enormous function is enormous. However all function pointers are the same size. Indeed, all pointers in C are the same size. This makes them easier for the system to deal with.

5.12: Complicated Declarations

We are basically skipping this section. It shows some examples more complicated than we have seen (but are just more of the same—one example is below). The main part of the section presents a program that converts C definition to/from more-or-less English equivalents.

Here is one example of a complicated declaration. It is basically the last one in the book with function arguments added.

    char (*(*f[3])(int x))[5]
  

Remembering that *f[3] (like *argv[argc] is an array of pointers not a pointer to an array, we can unwind the above to.

The variable f is an array of size three of pointers.

Remembering that *(g)(int x) = *g(int x) is a function returning a pointer and not a pointer to a function, we can further unwind the monster to.

The variable f is an array of size three of pointers to functions taking an integer and returning a pointer to an array of size five of characters.

One more (the penultimate from the book).

    char (*(f(int x))[5])(float y)
  

The function f takes and integer and returns a pointer to an array five pointers to functions taking a real and returning a character.