computronium
computronium

Reputation: 447

Is my understanding of how this C program works correct?

in the following program:

#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[]) {
        char *delivery = "";
        int thick = 0;
        int count = 0;
        char ch;

        for (int i = 0; i < argc; i++) {
                fprintf(stdout, "Argv[%i] = %s\n", i, argv[i]); /* printing to understand (ptu) */
        }
        while ((ch = getopt(argc, argv, "d:t")) != -1)
                switch (ch) {
                        case 'd':
                                fprintf(stdout, "Optind in case 'd': %i\n", optind);
                                delivery = optarg;
                                break;
                        case 't':
                                fprintf(stdout, "Optind in case 't': %i\n", optind);
                                thick = 1;
                                break;
                        default:
                                fprintf(stderr, "Unknown option: '%s'\n", optarg);
                                return 1;
                }
        fprintf(stdout, "Argc: %i\n", argc); /* ptu */
        fprintf(stdout, "Argv: %p\n", argv); /* ptu */
        argc -= optind;
        argv += optind;
        fprintf(stdout, "Optind: %i. Argc after subtraction: %i, Argv after increment: %p\n", optind, argc, argv);
        if (thick)
                fprintf(stdout, "Thick crust!\n");
        if (delivery[0])
                fprintf(stdout, "To be delivered %s\n", delivery);
        fprintf(stdout, "Ingredients:\n");
        for (count = 0; count < argc; count++)
                fprintf(stdout, "%s\n", argv[count]);
        return 0;
}

When I run the above program with arguments shown below I get the following output:

[u@h c]$ ./prog -t -d yesterday anchovies goatcheese pepperoni
Argv[0] = ./prog
Argv[1] = -t
Argv[2] = -d
Argv[3] = yesterday
Argv[4] = anchovies
Argv[5] = goatcheese
Argv[6] = pepperoni
Optind in case 't': 2
Optind in case 'd': 4
Argc: 7
Argv: 0x7ffebee8e498
Optind: 4. Argc after subtraction: 3, Argv index: 0x7ffebee8e4b8
Thick crust!
To be delivered yesterday
Ingredients:
anchovies
goatcheese
pepperoni

I would like to know if my understanding of what's going on under the hood is accurate, specifically for the argument parsing steps in the program. Apologies for not sharing a more minimal reprex, but in this case I probably couldn't. I wouldn't have spammed stackoverflow if I could show this to a friend who understood C. So, please bear with me. Here goes nothing:

  1. Defined main to accept command-line (cl) parameters. This requires two parameters:

    • integer argc which will contain the number of cl parameters including the name of the program, in this case it is 7
    • array of strings (i.e. array of char pointers), each element of which will point to the memory address of the first element of each string literal (stored in the CONSTANT memory block) passed in as a cl parameter to the program.
  2. for loop (self explanatory)

  3. On each run of the while loop getopt() will parse the argv[] array and assigns the next matching character from the optstring "d:t" to character variable ch, until it runs out of options (no pun intended) which is when it will return -1 and the control will exit the while loop.

    • at each such pass optind (which is initiated at 1 presumably because argv[0] is the program name) will be incremented to contain the index of the next element to be processed in argv... So in case 't', optind = <index of "-d" i.e. 2>, and in case 'd', optind = <index of "anchovies" i.e. 4> (this is because getopt() realizes from the ":" after 'd' in the optstring that -d will be followed by its optarg on the command line, thus optind is incremented to "4" here instead of being "3")
    • after -t and -d yesterday are processed getopt() can't find anything else in argv[] which matches elements in the optstring; thus it returns -1 and we break out of the while loop. optind remains set to 4 because getopt didn't find anything else after '-d' from the optstring.
  4. We now decrement optind's value "4" from argc to ensure we skip past the option arguments (which we have already parsed) to the remaining three non-option arguments. We also increment argv—which initially pointed to memory location of argv[0] i.e. "./prog"—by <optind * sizeof(char pointer on a 64-bit machine); i.e. 4 * 8> which is why argv now points 32 bytes ahead in memory: 0x7ffebee8e4b8 - 0x7ffebee8e498 == 0x20. In other words, argv[0] points to "anchovies"

  5. We then print stuff depending on values of thick, delivery and loop through the remaining non-option arguments to print them as well...

Upvotes: 5

Views: 239

Answers (1)

Nate Eldredge
Nate Eldredge

Reputation: 58142

Yes, your understanding is correct.

I would offer two notes:

  • "String literal" refers to a string that is actually defined within your program, as characters between "" delimiters. The "CONSTANT block" isn't a standard concept but I suppose you mean a block of read-only memory which is loaded from the binary, since that's where string literals normally reside. The strings which the argv pointers point to are not of this kind; they can't be, because they are not known when the binary is created. Instead, they are located in some unspecified region of memory, and you may modify them in place if you wish (though this is likely to make your code confusing); e.g. argv[0][3] = 'x' would be legal. (C17 standard 5.1.2.2.1 (2)).

  • Some people might likewise find it confusing to modify the values of argc and argv within main, and would suggest that you assign the modified values to some other variables instead:

int remaining_argc = argc - optind;
char **remaining_argv = argv + optind;

Upvotes: 4

Related Questions