Return to the lecture notes index

October 18, 2007 (Lecture 13)

Today's Example

Lab Discussion

We spent most of today discussing the current lab. It wasn't really a "Notable" discussion so much as a chance to read the description together, answer questions, &c.

"const"

The "const" qualifer can be used to give the compiler a hint about how a variable is to be used. It indicates that the variable is a "constant" -- that its value will not be changed by the programmer. If the programmer does try to reassign the varible, it will usually generate a compile-time "warning" ("discards qualifier") rather than an "error" -- the code will still compile. But, the warning properly indicates that there is a problem with the semantics.

We often use const, instead of #define to define constants. When we do this for global constants, we want to put the declarations into the "globals.c" file and the "externs" into the "globals.h" file:

  const double PI = 3.14;
  

"const *"

The declaration "const *" is a bit confusing. It declares a "pointer to a constant", not a "constant pointer". In other words, it is a pointer to a constant value -- not a constant pointer.

If one assigns a "const *" pointer to data that isn't actually declared as a "const", this isn't an error. Instead, it just generates a constant view of the data. In other words that, although the value might be changeable -- it isn't changeable via that particular pointer. The opposite is, of course, not safe -- and not allowed.

It is always legal to assign a "const" to a non-const -- this doesn't place the constant value in any danger.

Consider the example below:

  int x = 5;
  int y = 6;

  int *ip = &x;
  const int *cip = &x

  /* Legal */
  x = 7; 
  *ip = 8; 

  /* NOT Legal */
  *cp = 9;
  

"const" Pointer

What if we do want a "constant pointer", a pointer which cannot be reassigned? Well we can do this -- but the syntax looks really weird.

The name of the game here is that C, like other programming languages, is designed to make the common case convenient. As a consequence, the syntax for declaring constants favors constant values, not constant pointers -- constant pointers are just much less common.

Also do note that, just like any other constant variable, constant pointers should be initialized at declaration. If they aren't -- they can't later be assigned a value -- so they are basically not useful.

So, let's take a look at this by example:

  int x = 5;
  int y = 6;

  /* This declares a "constant pointer", the pointer itself can't be 
   * reassigned 
   */
  int * const cp = &x;

  /* This declares a constant pointer to constant data. 
   * The pointer can't be assigned. And, the value, itself, can't be 
   * changed via this pointer 
   */
  int const * const cpc = &x;

  cp = &y; /* NOT legal */
  *cp = 7; /* Legal */

  cpc = &y; /* NOT legal */
  *cp = 7; /* NOT legal */
  

"const" and Function Arguments

On many occasions we use "const" when we pass arguments to functions by reference -- but don't intend to change them. This, for example, often happens when we pass strings into functions. Since we don't have a first-class string type, but instead must use a pointer to an array of chars, strings are always passed by reference.

So, if a programmer is looking at a function prototype and notices that a string is being passed into the function -- it is unclear if the function intends to change the string or just read it. We can clarify the intent, an also protect against accidental misuse, by clarifying the intent.

Consider strcpy(), a function which copies from one string to another. Notice that the src is annoted as "const", but the destination isn't.

  int strcpy (char *dest, const char *src);
  

We run into an analageous situation when we pass structs by reference. We do this, need it or not, just for performance reasons. But, when we don't actually intend to change the struct, we should note that it is "const":

  int printRecord (const struct studentRec *student) {...}
  

We can pass "constant pointers" into functions. If we do this, it prevents the function, internally, from assigning the pointer to the address of a different object. But, in practice, this is almost never done.

First, since pointer, itself, is passed by value, the caller is protected from any changes -- they only affect the function's local copy. Second, it muddies up the interface -- the internal constant use is exposed to the caller who doesn't care. If it really is important to mark the variables as constant as a measure of safety within the function, they can be assigned to constant locals -- without it leaking out to the interface.

"const" and Return Values

This is a short section: There is no such thing as a "const" return value. A reutrn value is an "rvalue" by definition -- it can't be assigned, anyway.

Linked List Implementation

For fun, we went through and annoted various parts of our linked list code with "const". The updated verion is linekd at the top.

Guidelines

For our purposes, we should always use "const" in the circumstances below, all other uses are optional: