/* common C errors */
/* recall C compiler may not catch everything, or even warn */

int foo()
{
  void *buf = malloc(10); // malloc returns NULL, which is just a 0

  // 1. condition shortcuts
  // check if malloc failed
  if (buf == NULL)
    printf("error");
  // check if malloc succeeded
  if (buf != NULL)
    printf("ok");
  // shortcut: if condition not specified, defaults to "!= NULL"
  if (buf)
    printf("ok");
  // inverse shortcut: "!x" is same as "x == NULL"
  // means "if NOT "!= NULL") -- "!buf != NULL"
  if (!buf)
    printf("error");
  // careful not to confuse "!buf" with "!= NULL" and vice verse!
  // BEST: write out the full conditions, so you and others can tell exactly
  // what you meant.  Program/compiler will run just as fast.

  // Historic reason for shortcuts in C/UNIX?

  // 2. assignments
  // can put assignments inside conditions
  if ((buf = malloc(10)) != NULL)
    printf("ok");
  // depends on the act that even an assignment in C has a "value", which is
  // just the value assigned to the left-hand-size variable
  // that's why multiple assignments work
  x = y = z = 0;
  // above works b/c first  you do "z=0"; then the "value" of "z=0", which
  // is just "0", is then assigned to "y", in effect doing "y=0", etc.
  if ((buf = malloc(10))) // shortcut b/c don't have to say "!=NULL"
    printf("ok");
  if (buf = malloc(10)) // shortcut v2 b/c don't have to say "!=NULL"
    printf("ok");
  // if you wanted opposite condition
  if (!buf = malloc(10)) // syntax error, can't invert left-hand-size var
    printf("failed");
  if (!(buf = malloc(10))) // shortcut like buf == NULL
    printf("failed");
  // better to separate out assignment from comparison: same speed program/compiler
  buf = malloc(10);
  if (buf == NULL)
    printf("failed");
  // common mistake: mixing assignments w/ conditionals
  buf = malloc(10);
  if (buf = NULL) // note: just one '=' not '==' (FALSE b/c "NULL!=NULL" is untrue)
    printf("failed"); // never get executed!
  else
    printf("ok, ptr=%s", buf);
  // error above: if malloc() failed, it assigns buf=NULL
  //	cond. checks if NULL!=NULL, which is false, so branching to the
  //	"else" part, and running code that assumes that ptr is ok
  //    problem: we neglected to detect that malloc failed, assume ok, and
  //	we might be referencing a NULL ptr!
  // if malloc() succeeded, buf initially has a non-NULL value.
  //    cond, overrides the value of buf as NULL, compares NULL!=NULL, and
  //	branches to the "ok" part (assuming malloc succeeded).
  //    this causes two problems:
  //    1. memory leak, we lost the orig value from malloc, so can't free()
  //    2. same as if malloc failed -- i might deref a NULL ptr
  // malloc(3) may set errno=ENOMEM, but on newer libc implementations.

  // note other prog. languages avoid this, by using ":=" as the assignment
  // operator. this minimizes the chance of mistakes.
  // solution 1: swap the order of comparisons
  if (NULL == buf) // some companies' coding standards demand this form
    printf("failed");
  else
    printf("ok");
  // mistake, just one '='
  if (NULL = buf) // syntax error: can't assign to a non-variable (constant)
    printf("failed");
  else
    printf("ok");
  // solution 2: enable extra compiler checking, e.g., gcc -Wall
  if (buf = NULL)
    printf("failed");
  else
    printf("ok");
  // gcc will warn: "are you sure you mean to have an assignment in line
  // XXX?" gcc will offer to remove the warning, if you use double (())
  if ((buf = NULL))
    printf("failed");
  else
    printf("ok");

  // 3. "array bounds", loops running +/- one time than needed.
  // a class of bugs called "off by one" errors
  int len = 10;
  int a[len]; // already allocated (assume len=10)
  int i; // counter
  for (i=0; i<len; i++)
    printf("a[%d]=%d\n", i, a[i]); // print a[0]..a[9]
  // mistake
  for (i=0; i<=len; i++)
    printf("a[%d]=%d\n", i, a[i]); // print a[0]..a[10] a buffer overflow on a[10]
  // ok
  for (i=1; i<=len; i++)
    printf("a[%d]=%d\n", i-1, a[i-1]); // print a[0]..a[9]
  // error
  for (i=1; i<len; i++)
    printf("a[%d]=%d\n", i-1, a[i-1]); // print a[0]..a[8]

  // beware the inc/dec operators
  // post-incrementL i++
  // pre-increment: ++i
  // post-decrement: i--
  // pre-decrement: --i
  i = 4;
  i++; // post-increment
  printf("%d", i); // 5
  i = 4;
  ++i; // pre-increment
  printf("%d", i); // 5

  i = 4;
  printf("%d", ++i); // i is "pre-incremented", expr value is after
		     // increment, will print 5
  printf("%d", i); // print 5

  i = 4;
  printf("%d", i++); // i is post-incremented", expr value is BEFORE
		     // increment, will print 4
  printf("%d", i); // print 5

  // lots of bugs depend on how many times a loop can executed
  // number of times loops below will run will differ by +/- 1, meaning if
  // 'i' starts at, say, 4, some loops will run 3, 4, or 5 times.
  while (++i)
    do_something();
  while (i++)
    do_something();
  while (--i)
    do_something();
  while (i--)
    do_something();

  // solution: printf the value of your loop var inside a loop, and ensure
  // loops runs for exact no. of times needed, with the exact values that it
  // has to have.

  // you can iterate over a string as a pointer
  char *s = "hello world";
  while (++*s)			// NOTE: changes the 's' variable ptr
    printf("%c", *s);		/* Note: "*s" is same as s[0] */
  while (*s++)			// try this: is full string printing or not?
    printf("%c", *s);
  do {
    printf("%c", *s);
  } while (*s++)		// try this: is full string printing or not?
  do {
    printf("%c", *s);
  } while (++*s)		// try this: is full string printing or not?
}
