Monday, March 10, 2014

Constant Pointers and pointers to a constant (Ahhh... what was that?)

What do the following declarations mean?


  1. const int a; 
  2. int const a; 
  3. const int *a; 
  4. int * const a; 
  5. int const * a const; (or const int * const a;)


(1 & 2) The first two mean the same thing, namely a is a const (read-only) integer. 

(3) The third means a is a pointer to a const integer (i.e., the integer isn’t modifiable, but the pointer is). 

(4) The fourth declares a to be a const pointer to an integer (i.e., the integer pointed to by a is modifiable, but the pointer is not). 

(5) The final declaration declares a to be a const pointer to a const integer (i.e., neither the integer pointed to by a, nor the pointer itself may be modified).

Why to put so much emphasis on const, since it is very easy to write a correctly functioning program without ever using it. There are several reasons:

1. The use of const conveys some very useful information to someone reading your code. In effect, declaring a parameter const tells the user about its intended usage. If you spend a lot of time cleaning up the mess left by other people, then you’ll quickly learn to appreciate this extra piece of information. (Of course, programmers that use const, rarely leave a  mess for others to clean up…)

2. const has the potential for generating tighter code by giving the optimizer some additional information. 

3. Code that uses const liberally is inherently protected by the compiler against inadvertent coding constructs that result in parameters being changed that should not be. In short, they tend to have fewer bugs.

So You know pointers... hummm!!!

Using the variable a, write down definitions for the following:
(a) An integer
(b) A pointer to an integer
(c) A pointer to a pointer to an integer
(d) An array of ten integers
(e) An array of ten pointers to integers
(f) A pointer to an array of ten integers
(g) A pointer to a function that takes an integer as an argument and returns an integer
(h) An array of ten pointers to functions that take an integer argument and return an integer.



The answers are:
(a) int a; // An integer
(b) int *a; // A pointer to an integer
(c) int **a; // A pointer to a pointer to an integer
(d) int a[10]; // An array of 10 integers
(e) int *a[10]; // An array of 10 pointers to integers
(f) int (*a)[10]; // A pointer to an array of 10 integers
(g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
(h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer

Preprocessor directives: #error, #warning and conditional compilation


Conditional Compilation:

preprocessor conditional compilation directive causes the preprocessor to conditionally suppress the compilation of portions of source code. These directives test a constant expression or an identifier to determine which tokens the preprocessor should pass on to the compiler and which tokens should be bypassed during preprocessing. The directives are:
  • #if
  • #ifdef
  • #else
  • #ifndef
  • #elif
  • #endif
The preprocessor conditional compilation directive spans several lines:
  • The condition specification line (beginning with #if#ifdef, or #ifndef)
  • Lines containing code that the preprocessor passes on to the compiler if the condition evaluates to a nonzero value (optional)
  • The #elif line (optional)
  • Lines containing code that the preprocessor passes on to the compiler if the condition evaluates to a nonzero value (optional)
  • The #else line (optional)
  • Lines containing code that the preprocessor passes on to the compiler if the condition evaluates to zero (optional)
  • The preprocessor #endif directive
For each #if#ifdef, and #ifndef directive, there are zero or more #elif directives, zero or one #else directive, and one matching #endif directive. All the matching directives are considered to be at the same nesting level.
You can nest conditional compilation directives. In the following directives, the first #else is matched with the #if directive.
#ifdef MACNAME
                 /*  tokens added if MACNAME is defined */
#   if TEST <=10
                 /* tokens added if MACNAME is defined and TEST <= 10 */
#   else
                 /* tokens added if MACNAME is defined and TEST >  10 */
#   endif
#else
                 /*  tokens added if MACNAME is not defined */
#endif
Each directive controls the block immediately following it. A block consists of all the tokens starting on the line following the directive and ending at the next conditional compilation directive at the same nesting level.
Each directive is processed in the order in which it is encountered. If an expression evaluates to zero, the block following the directive is ignored.

When a block following a preprocessor directive is to be ignored, the tokens are examined only to identify preprocessor directives within that block so that the conditional nesting level can be determined. All tokens other than the name of the directive are ignored.

Only the first block whose expression is nonzero is processed. The remaining blocks at that nesting level are ignored. If none of the blocks at that nesting level has been processed and there is a #else directive, the block following the #else directive is processed. If none of the blocks at that nesting level has been processed and there is no #else directive, the entire nesting level is ignored.


#warning:
preprocessor warning directive causes the preprocessor to generate a warning message but allows compilation to continue. The argument to #warning is not subject to macro expansion.

#error:
The directive ‘#error’ causes the preprocessor to report a fatal error. The tokens forming the rest of the line following ‘#error’ are used as the error message.

You would use ‘#error’ inside of a conditional that detects a combination of parameters which you know the program does not properly support. For example, if you know that the program will not run properly on a VAX, you might write
     #ifdef __vax__
     #error "Won't work on VAXen.  See comments at get_last_object."
     #endif

If you have several configuration parameters that must be set up by the installation in a consistent way, you can use conditionals to detect an inconsistency and report it with ‘#error’. For example,
     #if !defined(FOO) && defined(BAR)
     #error "BAR requires FOO."
     #endif

The directive ‘#warning’ is like ‘#error’, but causes the preprocessor to issue a warning and continue preprocessing. The tokens following ‘#warning’ are used as the warning message.

You might use ‘#warning’ in obsolete header files, with a message directing the user to the header file which should be used instead.

Neither ‘#error’ nor ‘#warning’ macro-expands its argument. Internal whitespace sequences are each replaced with a single space. The line must consist of complete tokens. It is wisest to make the argument of these directives be a single string constant; this avoids problems with apostrophes and the like.

Notes:* - 
#ifdef identifier is the same is #if defined( identifier).
#ifndef identifier is the same as #if !defined(identifier).

Predefined Macros

The following macros are already defined by the compiler and cannot be changed.
__LINE__A decimal constant representing the current line number.
__FILE__A string representing the current name of the source code file.
__DATE__A string representing the current date when compiling began for the current source file. It is in the format "mmm dd yyyy", the same as what is generated by the asctime function.
__TIME__A string literal representing the current time when cimpiling began for the current source file. It is in the format "hh:mm:ss", the same as what is generated by the asctime function.
__STDC__The decimal constant 1. Used to indicate if this is a standard C compiler.