C/C++ assert Function

Discussion in 'C' started by poornaMoksha, Dec 30, 2011.

Tags:
  1. I have been writing many articles on C/Linux explaining concepts, APIs and their usage etc. This time I thought to write on a fundamental concept that every newbie should be well aware of in order to become a good coder, ie 'Good coding practices'. I have 3-4 broader level points to make here. I'll try to cover one point in this article while the others in the subsequent parts. I am sure this article will come in handy for the beginners.

    Defensive Coding



    For newbies I must say that writing a code that runs correctly in normal conditions is not very easy, the logic needs to be sound. But writing a code that behaves gracefully in all the adverse or failure conditions is even more hard. Here in this article we will demonstrate how some good coding techniques help to find bugs early that could easily escape the testing cycle.

    Here in this article, we will describe the concept of assertions.

    For assertion the macro/function used is 'assert()'.
    The signature of the macro/function is :

    Code:
    #include <assert.h>
    void assert(scalar expression);
    The macro/function assert() prints an error message to standard error and terminates the program by calling abort(3) if expression is false (i.e., compares equal to zero). The purpose of this macro is to help the programmer find bugs in his program.

    The assert macro/function



    A good coder should never underestimate a bug. He/she should keep in mind that even a small bug holds capacity to bring down his/her program. So, testing or finding bugs during development life cycles is first thing that should be done for making a bug free code.

    One of the most popular and simple mechanism used for fighting out small/big loopholes is the use of macro/function assert(). It takes a boolean argument or boolean result yielding expression as an argument and terminates a program with a log if the boolean expression or value is false. It does nothing if the expression/value is true.

    Lets take an example :

    Code:
    #include <sys/time.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <assert.h>
    #include <errno.h>
    #include <sys/resource.h>
    
    int main ()
    {
      // Define and object of structure
      // rlimit.
      struct rlimit rl;
    
      // First get the limit on memory
      getrlimit (RLIMIT_AS, &rl);
    
      printf("\n Default value is : %lld\n", (long long int)rl.rlim_cur);
    
      // Change the limit
      rl.rlim_cur = 100;
    
      // Now call setrlimit() to set the 
      // changed value.
      setrlimit (RLIMIT_AS, &rl);
    
      // Again get the limit and check
      getrlimit (RLIMIT_AS, &rl);
    
      printf("\n Default value now is : %lld\n", (long long int)rl.rlim_cur);
    
    
      // Try to allocate more memory than the set limit
      char *ptr = NULL;
      errno = 0;
      ptr = (char*) malloc(1024);
      assert(ptr != NULL);
    
      if(ptr)
      free(ptr);
    
      return 0;
    }
    In the above code we have used the getrlimit() and setrlimit() functions to limit the memory allocations. Now what we do is, we try to allocate more memory than the limit and assert out if the pointer returned by malloc() is NULL.

    Lets see the output :

    Code:
     $ ./assert 
    
     Default value is : -1
    
     Default value now is : 100
    Unexpected error.
    Aborted
    As we see that since the malloc returned NULL so the assert failed and we get an unexpected error. Please note that the error message from assert macro may depend on the compiler and C library being used. Depending upon these parameters you may also get a more meaningful error message.

    As you can see that assert takes an expression or a variable as argument and checks for its trueness. So, its always a good idea to use assert function wherever we know that a variable or an expression has to have a true value before proceeding further. So an assert once placed at these locations take care of the unexpected whenever it may occur. If the condition that is passed as an argument to assert is not true then that means there is some bug in your program. So we can use assert after memory allocations, before passing arguments to a function, checking the arguments inside a function etc.

    But there is an issue with this approach.

    Suppose there are 100 places where in we have used assert in our program or lets say there is a for loop where in we have used this macro. Then, if the code is written for a time critical app then assert may cause a huge overload which might not be suitable for time critical applications. So what do we do in this case??

    Well in this case we may apply a second technique of using compile time macros to guard assert(). This means that we will very well have assert all over in our code but those will be guarded by a macro which can be turned on and off during compile time. This means that if we encounter a bug that is not so easy to find then we can recompile the code with compile time macros on and see if an assert fails in this situation.

    Here is an example on how to use compile time macros to guard an assert :

    Code:
    #include <sys/time.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <assert.h>
    #include <errno.h>
    #include <sys/resource.h>
    
    int main ()
    {
      // Define and object of structure
      // rlimit.
      struct rlimit rl;
    
      // First get the limit on memory
      getrlimit (RLIMIT_AS, &rl);
    
      printf("\n Default value is : %lld\n", (long long int)rl.rlim_cur);
    
      // Change the limit
      rl.rlim_cur = 100;
    
      // Now call setrlimit() to set the 
      // changed value.
      setrlimit (RLIMIT_AS, &rl);
    
      // Again get the limit and check
      getrlimit (RLIMIT_AS, &rl);
    
      printf("\n Default value now is : %lld\n", (long long int)rl.rlim_cur);
    
    
      // Try to allocate more memory than the set limit
      char *ptr = NULL;
      errno = 0;
      ptr = (char*) malloc(1024);
    #ifdef DOASSERT
      assert(ptr != NULL);
    #else
      if(NULL == ptr)
      {
          return -1;
      }
    #endif
    
      if(ptr)
      free(ptr);
    
      return 0;
    }
    In the above code we have this time used a compile time macro that guards whether we need to assert or not. We can enable this macro during the compilation of the code.

    Here is the way we compiled the code :

    Code:
    $ gcc -Wall -g assert.c -o assert
    $ ./assert 
    
     Default value is : -1
    
     Default value now is : 100
    We see that we have not enabled the compile time macro yet and we do not get the error from assert as assert would have worked if the compile time macro would have been defined.

    Now lets define the compile time macro and see :

    Code:
    gcc -Wall -g -DDOASSERT assert.c -o assert
     $ ./assert 
    
     Default value is : -1
    
     Default value now is : 100
    Unexpected error.
    Aborted
    We see that this time we have enabled the compile time macro and we get the error from assert.

    So in this way we can still keep the assert statements though inactive but could get them active anytime we feel their requirement.

    There is another function 'assert_perror()' provided by Linux :

    Code:
    #define _GNU_SOURCE
    #include <assert.h>
    
    void assert_perror(int errnum);
    If the macro NDEBUG was defined at the moment <assert.h> was last included, the macro assert_perror() generates no code, and hence does nothing at
    all. Otherwise, the macro assert_perror() prints an error message to standard error and terminates the program by calling abort(3) if errnum is
    non-zero. The message contains the filename, function name and line number of the macro call, and the output of strerror(errnum).

    So we see that this function is more or less same as the assert() the only difference being that it displays the meaningful error which can otherwise be retrieved from strerror(errno) function.

    Note : I was expecting a meaningful error from assert() but I am not getting it. I am looking into it, I'll update the exact reason whenever I get one. Meanwhile, Anyones valuable comments/suggestions are most welcome on this :)

    Conclusion



    In this article, we tried to understand why good coding practices are required and we understood and discussed the usage of assert() function.

    Stay tuned for more.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice