CSE 271 Lab 4: Finishing Pointers in C

Announcements

Null pointers

Remember that a null pointer is a special value that is known to not point anywhere. Such a pointer is never valid. One way to get a null pointer is to use the constant NULL (which is really a macro for the number 0, much like the null character '\0' is also the number 0): and then you can test the value of ip to see if it is a valid pointer, as in Null pointers are used as markers to say that the pointer is not ready for use, or for failure when you would otherwise return a valid pointer. For example, the strstr(3c) function returns a pointer to the first occurrence of one string within another string, but returns a null if not found. Using null pointers also helps prevent the use of uninitialized pointers (e.g., those with undefined values) which can cause unrepeatable problems (i.e., difficult to debug).

Arguments to main()

On Monday we saw arrays and pointers and how similar they are. We can now figure out how the arguments to the required main() function work. The typical declaration for main is:
int main(int argc, char *argv[]) 
The first parameter, argc is simply the count of the arguments to the program. The second paramter has the argment values. Note that the order of those parameters is always argc followed by argv.

Now argv is a form we haven't discussed before. It is declared to be an array of character pointers. In fact, we'll treat it as an array of strings. Note that we could equivalently declare it to be a pointer to a character pointer (char **argv). Each string in the array contains one argument that was passed to the program, where each argument was separated by whitespace (one or more spaces or tabs). Note that the very first entry in argv[] (e.g., argv[0]) is a pointer to the name of the program that was executed.

Exercise 1. Write a simple program that prints out each argument, one per line, as in:

% ./printargs this is a test.
Program name: ./printargs
Number of arguments: 5
Arg 1: this
Arg 2: is
Arg 3: a
Arg 4: test.

Pointer Fun

Strings and pointer initialization can be tricky! To illustrate, let's explicitly try some of the things that we should not normally do. What does that mean? In this case, let us attempt to modify static strings and see what happens.

Exercise 2. Compile and run the following program:

int main(void) {
  char string1[] = "Hello 1";
  char *string2 = "Hello 2";
  string1[0] = 'J';
  //  string2[0] = 'J';

  return 0;
}
Then remove the comment markers to allow the string2[0] assignment, and compile and run it again. What was the result? The first assignment is fine; the second causes a crash! The first declaration created an array with the initial contents of "Hello 1". The second declaration created a pointer to a string constant, which might be placed in an area of memory that is read-only. By trying to modify the first letter of string2, we attempted to write to that read-only memory, which was trapped as an illegal operation.

Pointers to functions

Sometimes it is useful to keep track of functions dynamically (rather than, say, a variable and an extended switch() statement). C lets us create pointers to functions. When we declare such a pointer, we need to know not only that it is a pointer to a function, but also the return type and parameter types of the functions that the variable might point to. For example, to declare a variable fnPtr to be of type "pointer to function that returns a char * and takes an int as a single argument", we would use:
    char * (*fnPtr)(int);
Later we can assign fnPtr to a value, by using the name of a function that has already been declared:
    fnPtr = myfunction;
where myfunction is exactly of the type "returns a char pointer and takes a single int as argument". We can then call the function using the variable like any other function:
    char *c;

    c = fnPtr(4);

Variable Variations

Local and non-local variables

Variables that are declared within a block of code, such as the char string1[] array in the main() function above are called automatic local variables. They are automatic because they are created automatically when the function is executed, and they are local because their scope is only within the same block as their declaration.

Exercise 3. Compile and run the testlocal.c program. In this example, a pointer from a local variable is returned from a function. It turns out, in cases 1 and 2, that the local variable happens to be accessible even after the function has completed. However, we find in cases 3 and 4 that the value in memory is overwritten with the value of a later function. In case 3 the new value is recognizable but not in case 4.

We almost always use automatic local variables in our programs, but there are alternatives. One is to use global variables -- these are variables declared outside of any function, and are thus accessible anywhere within a program. Most of the time it is good programming practice to avoid using global variables. Another alternative is to declare a local variable with the additional static keyword.

When static is applied to an automatic local variable, that variable is is no longer automatically created whenever the function is executed. Instead, the variable is created (and initialized) just once, but is available for use every time the function is executed. It is still local in that no other function has access to the variable, though.

When static is applied to a global variable, that variable is no longer accessible to code outside of the current source file. This is useful when the variable is not meant to be accessible outside of this set of functions (data hiding).

Similarly, when static is applied to a function declaration, the function name is no longer available to any other function outside of the current source file. Again, this helps enforce modularity and data hiding.

Constant variables and parameters

If you've kept up with your readings, you saw the const qualifier which allows you to specify to the compiler those variables that will always have a constant value throughout execution. The typical example is for the value of pi:
        const double pi = 3.141592654;
The variable pi will now be considered to be read-only, and the compiler will warn you of any use that would attempt to write to that variable. The same use of const can be applied to other variable types, including arrays (or implicitly in string declarations, as we saw above).

We can also apply the const qualifier to pointers, but now we need to consider whether they apply to the pointer or to the data to which it points. That is, we can declare "constant pointers" or "pointers to constant data", or both, as in:

    int i = 4;

    int * const const_pointer = &i;
    const int *pointer_to_const = &i;
    const int * const const_pointer_to_const = &i;
Exercise 4: To test your skills in pointers, write a program that has two local integer variables with different values, prints out the values, and then calls a function that swaps the values of the two variables, and after it returns, the main() prints out the new values of the variables. Extend your implementation to use constant pointers as parameters to your swap function (preventing your function from inadvertently changing the pointers).


Last revised: 3 February 2013, Prof. Davison.