Uwe Ohse

C examples


First things first: Do not take these examples as guidelines for coding style. They are not. It's useful to know what's possible, but not everything possible is really useful in the real world.

the comma operator

Assume you want to replace your process with the logger utility which fortunately most if not all unix systems provide. Unfortunately this program is rarely at the same place in the different Unix systems. And assume that you don't care about $PATH, but want to try /usr/sbin/logger, /sbin/logger, /usr/bin/logger, /bin/logger and /etc/logger in that order, and want to print a simple error message to if nothing works. You then can write something like this:
      char *argv[]={"logger",NULL};
      _exit (42 != write (2, "no luck\n",
             -8*execv ("/etc/logger", (
              execv ("/bin/logger", (
               execv ("/usr/bin/logger", (
                execv ("/sbin/logger", (
                 execv ("/usr/sbin/logger", argv)
                ,argv))
               ,argv))
              ,argv))
             ,argv))
            ));
Note that this looks even more awful if you remove the spaces and the linefeeds - even indent and emacs can't get this code readable. That's intended, of course.

Do unterstand this beautiful (or not?) piece of code you need to know about the comma: It not only separates parameters, it's an real operator, too: Two expressions, which are separated by a comma, will be processed separated. First the left (including all side effects, including those affecting the right statement), then the right.
The comma operator may be used whereever an expression is allowed, but use parentheses around it at places where the comma is used for something else (functions parameters, some initializers, etc). This means that the comma in the following code is not an operator:

                 execv ("/usr/sbin/logger", argv)
It merely separates arguments. And using use the comma operator like here:
      for (i=0;i<10;i++)
      write(1,(i++,"bad style\n"+i),strlen("bad style\n"+i)-1);
is a very, very stupid thing to do: The C standard does only guarantee the "left then right" rule for the comma operator, which is the comma in the (i++,"bad style\n"+i) part, but not guarantee anything about the order in which the function arguments are handled - the strlen part may be processed before or after the other one. Here's your rope, be careful to not hang yourself, ok?
Summary about the comma operator:
      myfunction(i,i++); /* idiot, there is not comma operator involved */
      myfunction((i,i++),77); /* ok, although overcomplicated */
      myfunction((i++,i+2),42); /* ok */

Back to our original problem: What does this code do?

                execv ("/sbin/logger", (
                 execv ("/usr/sbin/logger", argv)
                ,argv))
The compiler has to process all arguments before it calls a function. Therefore it has first to call functions which are arguments. In this case it has to try to execute /usr/sbin/logger/ first, and then it tries the next one. And so on - that's the whole story behind the chain of execv's.

Reducing the number of execv to 1 we get:

      _exit (42 != write (2, "no luck\n",
             -8*execv ("/etc/logger",argv)
            ));
execv returns a -1 on error (and doesn't return on success). Multiply -8 * -1 to get 8, 8 ist the length of "no luck\n". And 42 is something the write will surely not return, so "42 != write()" equals 1: The program exists with a exit code of 1.

the comma operator, obfuscecated

Using the preprocessor we can make the code more compact:
 #define E(a,b) execv(a "/logger",b)
 #define F(a,b) E(a,(b,argv))
      _exit(42!=write(2,"no luck\n",-8*F("/etc",F("/bin",
      F("/usr/bin",F("/sbin",E("/usr/sbin",argv)))))));