6.4. Diagnosing run-time errors

Although your program may compile and link successfully, you may still encounter unexpected results during execution. Programming errors that do not violate the syntax of the language are not necessarily flagged by the compiler. This section describes some of the most common errors, how to detect them, and what you can do to rectify the problem.

6.4.1. Uninitialized variables

Unlike variables with static storage duration, according to the C and C++ programming language standards, an automatic (that is, stack allocated) storage duration object is not implicitly initialized, and its initial value is indeterminate. In other words, there is no guarantee that if an auto variable is used before it is set, it will yield the same result in every execution of the program. For example, when you first execute the following program, it may seem to be working correctly:

$ cat initauto.c
#include <stdio.h>
void func(int *p)
{
    if (p == NULL)
        printf("NULL pointer.
");
    else
        *p = 0xdeadbeef;
}

void main()
{
    int *ptr;
    func(ptr);
}
$ cc initauto.c
$ a.out
NULL pointer.

However, there is still the potential that, given the right conditions, the program will start to fail and yield unexpected results.

The C and C++ compilers provide the -qinitauto option, which causes the compiler to generate extra code to initialize all automatic variables to a specified initial value. Due to the extra code that the option generates, it reduces the run-time performance of your program, and its use is only recommended for debugging only. For example, compile and execute the above program with -qinitauto=FE and you will receive a different result indicating a potential problem:

$ cc -qinitauto=FE initauto.c
$ a.out
Segmentation fault(coredump)

To find out where auto variables are used before being set, use the -qinfo=gen compiler option:

$ cc -qinfo=gen initauto.c
"initauto.c", line 13.10: 1506-438 (I) The value of the variable "ptr" may be
used before being set.

6.4.2. Run-time checking

The -qcheck option inserts run-time checking code into the program executable. The following checks are supported:

NULL pointerChecks if the pointers used in any pointer referencing have addresses greater than 512.
Array boundsChecks for array indexing to be within the array bounds, when the size of the array is known at compile time.
Divide by zeroChecks for integer divide by zero.

Depending on the suboptions specified, if a violation occurs, a run-time SIGTRAP exception is raised. You can provide your own signal handler to handle the exception:

$ cat check.c
#include <stdio.h>

#ifdef DEBUG
#include <signal.h>
#define SIGNAL(sig, handler) signal (sig, handler)
void trap_handler(int sig)
{
    printf("SIGTRAP handled
");
    exit(-1);
}
#else
#define SIGNAL(sig, handler) ((void)0)
#endif

void func(int *p)
{
    printf("p has address %p
", p);
    *p = 0xdeadbeef;
}

void main()
{
    SIGNAL(SIGTRAP, trap_handler);
    func(NULL);
}
$ cc -DDEBUG -qcheck check.c
$ a.out
p has address 0
SIGTRAP handled

Similar to the -qinitauto option, the -qcheck option reduces the run-time performance of your program. It is recommended that you use it only for debugging.

6.4.3. Unsignedness preservation in C

If you use the cc command, specify the -qlanglvl=extended option, or the -qupconv option, and the C compiler will use unsignedness preservation in integral promotion. This was the semantic used prior to the c89 standard, also commonly referred to as K&R C.

Unsignedness preservation promotes unsigned integral types smaller than int, that is, unsigned char and unsigned short, to unsigned int. On the contrary, c89 and c99 mandate value preservation, where they are promoted to int. Unsignedness preservation may result in unexpected execution behavior. For example:

$ cat upconv.c
#include <stdio.h>
void main()
{
    unsigned char zero = 0;
    if (-1 < zero)
        printf("NOUPCONV: Value-preserving rules in effect 
");
    else
        printf("UPCONV: Unsignedness-preserving rules in effect 
");
}
$ cc upconv.c
$ a.out
UPCONV: Unsignedness-preserving rules in effect
$ cc -qnoupconv upconv.c
$ a.out
NOUPCONV: Value-preserving rules in effect

6.4.4. ANSI aliasing

The standard conforming language levels supported by the C and C++ compilers enforce a type-based aliasing rule during optimization. Type-based aliasing, sometimes referred to as ANSI-aliasing, restricts the lvalues[2] that can be safely used to access a data object. In essence, it mandates that a pointer can only point to an object of the same type. In other words, any pointer cast followed by a dereference violates this rule. The only exceptions are:

[2] An lvalue is an expression with an object type. It is a locator value representing an object.

  • The sign and type qualifiers are not subject to type-based aliasing

  • A character pointer can point to any type.

For example, the following program gives different results when optimization is in effect:

$ cat ansialias.c
#include <stdio.h>

unsigned int rc;
unsigned int function( float *ptr )
{
    rc = 0;
    *ptr = 1;
    return rc;
}

void main()
{
    unsigned int x = function((float *)&rc);
    printf("x = %x rc = %x
", x, rc);
}
$ c89 ansialias.c
$ a.out
x = 3f800000 rc = 3f800000
$ c89 -0 ansialias.c
$ a.out
x = 0 rc = 3f800000

When optimization is turned on with -O, the compiler assumes that the pointer ptr cannot be pointing to the external variable rc due to type-based aliasing. Hence, it can return the value 0 to the caller because of the rc = 0 assignment.

If you use the cc compiler driver, or specify the -qalias=noansi option, the compiler makes the worst case aliasing assumptions, and assumes that a pointer of a given type can point to an external object or any object whose address is already taken, regardless of type. This will correct the programming error in the above example. However, it also greatly reduces optimization opportunities and degrades run-time performance. It is therefore recommended that you change you program to conform to type-based aliasing rule.

6.4.5. #pragma option_override

Unfortunately, it may not be easy to fix all programming errors in a complex application, especially when the error only shows up when optimization is used. In this case it may be worth while to turn off optimization for the function where the programming error is, while the rest of the program still benefits from optimization.

Use the #pragma option_override directive to specify alternate optimization options for specific functions. In the example in 6.4.4, “ANSI aliasing” on page 247, adding the directive:

#pragma option_override(function, "opt(level,0)")

to the source will correct the programing error:

$ c89 -0 ansialias.c
$ a.out
x = 3f800000 rc = 3f800000

The #pragma option_override directive is also useful in finding out, by a process of elimination, which function is causing the problem. By selectively turning off optimization for each function within the directive until the problem disappears, it will let you identify where the programming error resides.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset