next up previous
Next: About this document ...

CSCI.1200 Computer Science II
Summer, 2002
Worksheet 5
More on Pointers

Reading: Deitel & Deitel, Chap 4.9, 5.9, 5.10

Two dimensional arrays

C++ allows you to create two dimensional arrays, as shown in the following example:

   1  int main(){
   2      int c = 1,
   3          r = 0;
   4      double arr[2][3];
   5      arr[r][c] = 3.141592;
   6      return 0;
   7  }

When a two dimensional array is passed as an argument to a function, the size of the second dimension must be included in the function definition. If the array arr as defined above were to be passed as an argument to a function, here is what the function definition would look like.
void Fctn(double arr[][3])
Two nested for statements with two loop iterators are needed to access all values of a two dimensional array as shown in the following example, which constructs and prints a multiplication table.
     1	#include <iostream>
     2	using namespace std;
     3	 
     4	 void fill_table(int table[][10]){
     5	   for (int row = 0; row < 10; ++row){
     6	     for (int col = 0; col < 10; ++col){
     7	       table[row][col] = row * col;
     8	     }
     9	   }
    10	 }
    11	 
    12	 void print_table(int table[][10]){
    13	   for (int row = 0; row < 10; ++row){
    14	     for (int col = 0; col < 10; ++col){
    15	       cout << ' ' << table[row][col];
    16	     }
    17	     cout << endl;
    18	   }
    19	 }
    20	 
    21	 int main(){
    22	   int table[10][10];
    23	 
    24	   fill_table(table);
    25	   print_table(table);
    26	   return 0;
    27	 }

You can allocate arrays of larger dimensions as well, but it is seldom done. C++ allows arrays as large as 12 dimensions. Whenever a multidimensional array is passed to a function, all dimension sizes except the first one must be given in the function header and prototype.

Exercise 1: Write a program which prompts the user to type in five character strings into the array char input[5][32]. Use a loop to input the five strings. Then pass this array as a parameter to a function named uppers, which will count the number of uppercase characters entered in each of the five strings, and return that number to main. Print out the value from the function and return.

Strings

One of the most confusing things about pointers is that they seem to refer to both addresses and arrays. This confusion is most common with strings. Recall that a string is an array of characters terminated with '\0'.

Consider this line of code:
char *p = "This is a string";
The variable p contains an address in memory, 30,000 for example. The byte at address 30,000 contains 84 (ASCII code for 'T'), the byte at address 30,001 contains 104 (ASCII code for 'h'), the byte at address 30,002 contains 105 (ASCII code for 'i'), the byte at address 30,003 contains 115 (ASCII code for 's'), the byte at address 30,004 contains 32 (ASCII code for a space), and so on. The byte at address 30,016 contains '\0'. The contents of memory location 30,017 and beyond are indeterminate.

However, note that the variable p contains only a single value, 30,000, the address of the head of the string.

You can print out a string using cout by using the address of the string as the operand.

cout << p << endl;

prints the line This is a string on the terminal.

When you pass a pointer as an argument to a function, only the address is copied. However, the function can change the contents of the memory that the pointer is pointing to. Here is an example:

#include <iostream>
using namespace std;
void makeupper(char *); // function prototype
int main()
{
   char *p = "This is a string";
   char q[80];
   strcpy(q,p);
   makeupper(q);
   cout << q << endl;
   return 0;
}

void makeupper(char *s)
{
    while (*s != '\0') {
       if (*s >= 'a' && *s <= 'z') 
          *s -= 32;
       s++;
    }
}

Arrays of Pointers

It is possible to create arrays of pointers. Typically these are arrays of pointers to characters (i.e. arrays of character strings), but arrays of pointers to any data type are permitted. The following statement declares an array s of five pointers to character strings:
char *s[5];
Note that after this declaration, none of these pointers have been initialized. However, they can be assigned in ways we've already seen. Here are some examples with a memory diagram:


\begin{picture}(320,90)(-150,10)
\put(65,88){s}
\put(60,10){\framebox (15,15)}
\...
...nt
s[1] = ''Grumpy'';\\
s[2] = new char[100];\\
s[3] = s[1];
}}}
\end{picture}

Note also that the following statement would cause a memory exception error:
strcpy(s[0],"Happy");
These arrays can be initialized by enclosing the strings in curly braces, separated by commas. Here is an example:
char *dwarfs[] = {"Happy","Grumpy","Sneezy","Sleepy",
"Bashful","Doc","Dopey"};
Note that when an array is initialized in this way, it is not necessary to specify the size of the array. The implied number in the square brackets is 7 in the above example because there are seven character strings (dwarfs).

You could print Grumpy with this statement:
cout << dwarfs[1];

In some ways, an array of pointers is like a two-dimensional array. For example, you could print the p in Grumpy like this:
cout << dwarfs[1][4];
You could also print the p in Grumpy like this:
cout << *(dwarfs[1]+4);

This statement is pretty ugly, and most people don't write code that looks like this, but it is instructive. To analyze it, start from inside the parentheses. dwarfs[1] is a character pointer because dwarfs is an array of character pointers. Suppose this address is 500,000. This means that the character G is at memory address 500,000, the character r is at memory address 500,001, the character u is at memory address 500,002, and so on. The expression dwarfs[1] + 4 returns an address four bytes higher than dwarfs[1], in other words, the memory address 500,004. The * operator takes a pointer as an operand, goes to that address and gets the value, in this case the character p.

Since an array variable is really a pointer, an array of pointers can be thought of as a pointer to a pointer. (In fact, you will sometimes see char *varname[]; written as char **varname;.) Here is a third, even uglier way to print the p in Grumpy, that uses no array brackets at all:
cout << *(*(dwarfs+1)+4);

Exercise 2: What would the following program print? If the program would generate either compile time errors or run time errors, indicate this and explain the problem, but then continue on as if the error had not occurred. (Answer at the end of the worksheet.)


#include <iostream>
using namespace std;
int main()
{
    char *s[6];
    s[0] = "Mickey Mouse";
    s[1] = "Donald Duck";
    strcpy(s[2],s[0]);
    s[3] = new char[30];
    strcpy(s[3],s[1]);
    s[4] = s[3];
    char *p = s[1];
    cout << p << endl;
    p += 2;
    cout << p << endl;
    p = s[3];
    p += 2;
    for (int i = 0; i < 6; ++i)
        cout << *(p++);
    cout << endl;
    *p = 'X';
    cout << s[3] << endl;
    cout <<  *s[0] << endl;
    cout << (int) *(s[0]+6) << endl;
    char **pp;
    pp = s;
    pp++;
    cout << *pp << endl;
    return 0;
}

Pointer Tricks and Traps

Many of the hard-to-find bugs in programs that you write in the future will result from the misuse of pointers. Since array indexing in C++ gives no indication when you reach the end of the array, it is very easy to overwrite other variables. Here is a simple example where the strcat function is misused (recall that strcat concatenates the string in its second argument onto the end of the string in its first argument):


#include <iostream>
using namespace std;
int main()
{
   int i;
   char *dwarfs[] = {"Happy", "Grumpy", "Doc", "Sleepy", "Bashful"};
   for (i = 0; i < 3; ++i)
       cout << dwarfs[i] << endl;
   cout << endl;
   strcat(dwarfs[1]," loves Snow White");
   for (i = 0;i < 3; ++i)
       cout <<dwarfs[i] << endl;
   return 0;
}
You would expect the output of this program to look like this:


Happy
Grumpy
Doc

Happy
Grumpy loves Snow White
Doc

but in fact the output with most compilers would be this:


Happy
Grumpy
Doc

Happy
Grumpy loves Snow White
loves Snow White

Notice that the string dwarfs[2] has been changed although there was no statement changing it. This is because the string pointed to by dwarfs[1] was only seven (not six!) characters long, so when you concatenated another string onto it, other memory was overwritten. It's not guaranteed that the memory for dwarfs[2] immediately follows that of dwarfs[1], but most compilers will allocate memory in this way.

Assuming that the strings are stored contiguously, the memory layout of dwarfs would look something like this:


\begin{picture}(480,100)
\par\put(49,90){dwarfs}
\put(60,10){\framebox (15,15)}
...
...ector(2,-1){20}} \put(90,5){\tiny points to 'B' in \lq\lq Bashful''...}
\end{picture}

If the value of dwarfs[0] (i.e. the start of the string Happy) is address 5000, then the value of dwarfs[1] is 5006, the value of dwarfs[2] is 5013 and so on. When you concatenate the string "loves Snow White" onto the string Grumpy, you will overwrite Doc, Sleepy and part of Bashful.

No error or warning messages would be produced, and you might not even have discovered the error until much later, and so finding this bug would have been extremely difficult.

Another common type of error occurs when you use the assignment operator = on a string when you should use strcpy. The assignment operator on pointers makes both pointers point to the same memory, and so subsequent changes to the contents of this memory appears to change both values. Here is an example of erroneous code which demonstrates this. The program asks the user to enter three strings, and it stores these three strings in an array called storage. It then prints the three strings.


 
#include <iostream>
using namespace std; 
int main() 
{
   char *in = new char[80];
   char *storage[3];
   int i;
   for (i = 0; i < 3; ++i) {
       cout << "Enter a string: ";
       cin >> in;        // user enters a character string
       storage[i] = in;  // save the string 
   }
   for (i = 0; i < 3; ++i)
       cout << storage[i] << endl;
   return 0;
}

You would expect that this would print out the three strings that were entered, but in fact it prints the last string three times. This is because the program uses the = operator on pointers when it should have allocated new memory and used strcpy. All three cells in storage contain the address of in.

Here is a correct implementation of this program.

#include <iostream>
using namespace std;
int main()
{
   char *in = new char[80];
   char *storage[3];
   int i;
   for (i = 0; i < 3; ++i) {
       cout << "Enter a string: ";
       cin >> in;
       storage[i] = new char[strlen(in)+1]; // Why must we add 1 here?
       strcpy(storage[i],in); 
   }
   for (i = 0; i < 3; ++i)
       cout << storage[i] << endl;
   return 0;
}

Exercise 3: Modify the code you wrote for Exercise 1 in two steps (finish the first before trying the second):

  1. Change the input array to this declaration: char * input[5]. In your input loop, read each string into an array char temp[80], allocate to input[i] the exact amount of memory needed to store the ith string (for each input string $0\leq i < 5$), and copy the string from temp. Follow the example above.

  2. Rewrite the uppers function using only pointer arithmetic instead of array brackets.

Yet a third common type of error involves the == operator on pointers. Novice programmers often use this to test whether two strings have the same contents, when they should be using the strcmp(char *s1, char *s2) function. The == operator on pointers checks to see if the two pointers are pointing to the same memory address. Here is an example:


#include <iostream>
using namespace std;
int main()
{
   char *s1 ="dog";
   char *s2 = new char[4];
   strcpy(s2,s1);
   if (s1 == s2) 
      cout << "True" << endl;
   else
      cout << "False" << endl;
   return 0;
}

You might expect that this program would print True because both strings are the same, but in fact it prints False because s1 and s2 point to different addresses. Here is a corrected version of this program:


#include <iostream>
using namespace std;
int main()
{
   char *s1 ="dog";
   char *s2 = new char[4];
   strcpy(s2,s1);
   if (strcmp(s1,s2) == 0) 
      cout << "True" << endl;
   else
      cout << "False" << endl;
   return 0;
}

This prints True as you would like. Recall that the strcmp function returns 0 if the two strings are the same.

Exercise 4: What would the following program print? If there are statements that would cause errors, please indicate this and continue as if the statement were not there. (Solution is at the end of the worksheet.)


#include <iostream>
using namespace std;
int main()
{
    char *ptrs[6];
    ptrs[0] = new char[64];
    strcpy(ptrs[0],"abcdefg");
    ptrs[1] = "Here is a typical string of characters";
    ptrs[2] = ptrs[0];
    strcpy(ptrs[2],ptrs[1]);
    cout << ptrs[0] << endl;
    ptrs[3] = new char [16];
    ptrs[4] = "Another string";
    strcpy(ptrs[3],ptrs[1]);
    strcpy(ptrs[5],ptrs[4]);
    strcpy(ptrs[0],ptrs[1]);
    cout << ptrs[0]+5 << endl;
    if (ptrs[1] == ptrs[0]) 
       cout << "True" << endl;
    else
       cout << "False" << endl;
    if (ptrs[0] == ptrs[2])
       cout << "True" << endl;
    else
       cout << "False" << endl;
    ptrs[6] = new char[16];
    strcpy(ptrs[6],"12345");
    return 0;
}

Passing arguments to programs, argc and argv

When a program is run from a console window by typing the command, it is possible to pass arguments to your program 1These arguments are separated by one or more white spaces. So far we have written main as if it had no arguments, but in fact it has two arguments. The first argument is of type int and is always called argc, which stands for argument count, the number of arguments, including the name of the executable itself. The second argument is an array of character pointers which point to the arguments. By convention, this is always named argv for argument vector. For example, if you typed myprog.exe hello world, argc would have the value 3, argv[0] would be "myprog.exe", argv[1] would have the value "hello", and argv[2] would have the value "world".

Here is a program which simply prints out its arguments. Compile and run it with various arguments.

#include <iostream>
using namespace std;
main(int argc, char *argv[])
{
    for (int i = 1;i<argc;i++)
         cout << argv[i] << " ";
    cout << endl;
}

You can pass arguments to your programs when you run them in Visual Studio by choosing Settings from the Project menu, (Alt-F7), and choosing the Debug tab. You can type your arguments in the Program Arguments window.

Exercise 5: Write a program that takes any number of arguments and displays the number of vowels in all of them (excluding 'y'). For example, the following command:
a.out Grumpy Sleepy Happy Doc
would display
There were 5 vowels

Solution to Exercise 2:

The line strcpy(s[2],s[0]); will cause a run time error because s[2] does not point to valid memory. When this line is deleted, the output looks like this:

   
Donald Duck
nald Duck
nald D
Donald DXck
M
32
Donald Duck

Solution to Exercise 4:
The line strcpy(ptrs[5],ptrs[4]); will generate an error since ptrs[5] does not point to valid memory. The lines strcpy(ptrs[3],ptrs[1]); and ptrs[6] = new char[16]; are not necessarily error-producing. However, they could produce errors, depending on where the compiler allocated the various pieces of memory. Removing those lines, the output is:

Here is a typical string of characters
is a typical string of characters
False
True



next up previous
Next: About this document ...
Paul Lalli 2002-05-17