Solved

Array, pointers, &operator

Posted on 2000-03-01
23
528 Views
Last Modified: 2013-12-14
Below is an example of a snippet of code followed with questions about ptrs and arrays.

line1      int a[10] = {2,4,8,16,32,64,0};
line2      int *p = 0;
line3      int (*z)[10];

line4      p = &a;
line5      p = &a[2];
line6      z = a;
line7   z = &a;

Note I have a background in assembly, so please be somewhat low-level in your answer.
line4 will produce the error "cannot convert from int (*)[10] to int (*)
line6 will produce the error "cannot convert from int [10] to int (*) [10]

Also, why does "= &a" result in the type int (*) [10]?
The syntax is bothersome. Understanding what is occuring low-level would help.

p=a is same as p=&a[0]
z=&a is ?

Please be as detailed as possible (lexical, grammar...in-depth explanation.)      

0
Comment
Question by:Paullkha
  • 9
  • 7
  • 3
  • +3
23 Comments
 

Expert Comment

by:salnem
Comment Utility
First, 'a' is not really type int[10], it is type int*.  It points to the first element of the array of integers. Therefore, when you do "p=a" you are assigning the value of p to that of 'a'.  Effectively, this makes the memory address that 'p' points to the same as the one 'a' points to.  After that assignment, you can access the array defined by 'a' using 'p' (p[3] is then the same as a[3]).

i.e. if 'p' was pointing to 0x000 and 'a' was pointing to 4x234 before the assignment, 'p' is pointing to 4x234 and 'a' hasn't changed after the assignment.

Second, "p=&a" is trying to give 'p' the value of a[0](in this case 2).  So when you assign "p=&a", you are making 'p' point to 0x002.  The '&' operator gives the "stuff" stored at the memory address of a pointer.

  i.e. if 'p' was pointing to 4x234 and 4x234 contained the number 3, if you executed
  "cout << &p;" you would get 3
  "cout << p;" you would get 4x234

Note: this might not be the book correct definition of the things I described, but it sure works for me.
0
 
LVL 2

Author Comment

by:Paullkha
Comment Utility
I do not believe your comments are in aggreement with the VC++ debugger.

p=&a will not compile.
0
 
LVL 2

Author Comment

by:Paullkha
Comment Utility
Edited text of question.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
>> First, 'a' is not really type int[10], it is type int*.
Actually it is both.

In C and C++ (As in assembly) pointers and arrays are very closely related.  when you create a names array like

int a[10];

"a" functions in every way like a constant pointer.  (not a pointer to a constant, but a pointer that IS constant.)  i.e "a" acts like a pointer to 10 ints in a row memory.  a canot be changed, but it can be used just like a pointer.  i.e you can do

int *b = a;

or

int *c = a + 3;

continues.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
Of course you can't do something like

*a++

as this tries to change the "a pointer" which is not allowed.

So looking at the error message

>> p = &a;
>> "cannot convert from int (*)[10] to int (*)
well that makes sense.  a acts like "int[]" or like "int *" already.  So when you take its address with & you are creating a type like "int **".  i.e you are adding an extra level of indirection.  All you need is

p = a;

because the two have nearly identical types.



Now in
>> int (*z)[10];
>>  z = a;
>>"cannot convert from int [10] to int (*) [10]
z has a type like "int **".  i.e. it is a double indirection pointer.  But a is only a single indirction pointer, like "int *" so the two are not the same type.  However, the address of a ("&a") has a type of "int **" so it is compatible with z.  That is why the next line

 z = &a;

succeeds.
0
 

Expert Comment

by:chinni021900
Comment Utility
Hi Paullkha,
                      line1 int a[10] = {2,4,8,16,32,64,0};
                      line2 int *p = 0;
                      line3 int (*z)[10];

                      line4 p = &a;
                      line5 p = &a[2];
                      line6 z = a;
                      line7   z = &a;
I will try to explain line by line ok.
line1:  Here a is declared as a const pointer to an array of integers.
           'a' has the value an address pointed to the first element, =&a[0]
line 2: 'p' is a pointer pointing to an integer. This case it contains an address value equal to 0.
line 3: Here 'z' is declared as const pointer to an array of integer pointers.
          z contains an address points to the first element of the array, which itself is a pointer.
z=&z[0]  and z[0] is an address. Clear.
line 4: p is a pointer var to int and a is a constant pointer to an arrray of integers. Right.
 Now if u want to access the array with 'p', this will do that;
              p=a;
         or p=&a[0];
Both statements are equal.
If u try to do this: p = &a;
It is trying to access the address of pointer variable a, which is of type int(*)[ ].
Let us take an example:
Say the array elements are stored from the address starts with 2000.
Variable 'a' is stored at 1000.
So, &a = 1000 and a=2000. Clear.  You may think that , why don't it just take the address(numerical value) atleast. When u r assigning the values, both operands should be of same type or a type conversion is defined for them. Here p can only store an address of integer but not a pointer to an integer( or  any other type). Here it is trying to store an adress of a pointer to an array. So u gets an error.

LIne 5: You r just assingning the address of  3rd element to p.
Line 6: z is a pointer to an array of integer pointers. And a is apointer to an array of integers.
Foremost thing is that you can not assign a value to z, because it is a const pointer. And the operands of = are not matching.
You can assign like this:
z[0]=a; This works fine since z[0] is a pointer to int and a aswell.

I hope now u understand a pointer to a variable,
                                             a pointer to an array of data type
                                            a pointer to an array of pointers

 
0
 

Expert Comment

by:chinni021900
Comment Utility
Hi,
THis is a disc on how pointers behave and stored. THis may help
in understanding the concepts.
Take this statement:
int x=20;
Say, x is allocated a memory starting from 2000, the no. of bytes to store an integer. ( 2/4/x depends on ur platform). Say two bytes are allocated.
Address: 2000
address: 2001
are allocated for x, and compiler decides to store an integer at thes bytes.
The numerical value 20 is converted and stored as two bytes.
It may go like this:
2000 contains 20/256
2001 contains 20%256
When u access integer it will convert back to the value in the reverse way.
Fine!
The value stored is a formatted vlue and only the accessor variable
decides how to convert the value. If u access these memory locations using a char pointer, you can read and process byte by byte. If u access
the locations with a float pointer, it will take those two bytes and the next two bytes and then convert it back to the value. ( float needs 4 bytes say).
So the compilers will take the responsibility to assign an address to a particular type of data properly.
Now say int *ptr = &x;
ptr will be stored at (say) 2500;
The value stored at this address is
2500 address :  2000 value
The declaration  means that   ptr is a pointer variable and the address pointed to by the ptr can store integer only. This must be ensured as you are entitled to access the value by dereferencing operator
int value = *ptr;  
Since ptr is an integer ptr, compiler understands what type conversion to  
get back the value pointed to by it.
Hope this makes some more cocepts clear to.
have a nice day
chinni


0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
chinni, where exactly was salnem and i wrong?  I mean we must have been wrong, that is why you answered, right?

FYI
>>ine 3: Here 'z' is declared as const pointer
>> to an array of integer pointers.
its is not a const pointer.

>> Here p can only store an address of integer
>> but not a pointer to an integer( or  any other type)
No p can store a p can store a pointer to an integer.
C++ does not have a concept of "address".  Although in most implimentations a pointer and an address are the same, so what does this statement mean?

>> Foremost thing is that you can not assign a
>> value to z, because it is a const pointer.
You can assign a value to "z" because it is NOT const.

>> If u access these memory locations using a char
>> pointer, you can read and process byte by byte.
Not necessarily.  The C++ standard does not guarantee that and some platforms would not allow it.

>> The value stored at this address is
>> 2500 address :  2000 value
That is intirely implimentation defined.  And no implimentation can store all pointers this way.  Pointer to members have to be of a more complex nature.
0
 
LVL 2

Author Comment

by:Paullkha
Comment Utility
Nietod - chinni does make Many mistakes.
However, one thing I would like to have more clear that chinni brought up.

int a[10]

a is considered a variable stored at say mem loc 1000. This location may contain the value 2000, which is where the array as a continous block...was this an accurate statement?

4 instance:
int x = 3;
int *p = x;
int *n;
n=p;

x:100
loc 100 contains value 3

p:1000
loc 1000 contains value 100

n:2000
loc 2000 will be assigned value of p, which is 100

int a[10];
int *p = a;
int *n;
n=p;
how is the array setup?

0
 
LVL 2

Author Comment

by:Paullkha
Comment Utility
chinni - A few too many mistakes.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
>> Nietod - chinni does make Many mistakes.
My complaint was that salnem was here first and answered the question for the most part.  It seems like he--or maybe me--deserves the credit.  For someone to come along and repeat the existing information as an answer is just greedy.

>> int a[10]
>>
>> a is considered a variable stored at say
>> mem loc 1000. This location may contain
>> the value 2000, which is where the
>> array as a continous block...was this an
>> accurate statement?
I think what you are asking about is how is "a" stored and how is the array it points to stored.  is that right?

Well this is all implimentation defined, but on a typical implimentaiton "a", that is the pointer to the first integer in the array, is not stored.  i.e. there is no pointer variable actually allocated in that above statement.  The only thing allocated is the array of ints.  However, when you use the symbolic name "a" in your code you are using a value that can be treated like a pointer by the compiler.  Like if you assign "a" to a "int *" pointer the compiler will store a pointer to the first integer in the array in the pointer.  

In your example,

int x = 3;
int *p = x;

would have to be

int *p = &x;

In that case you analysis would be correct on systems that store simple addresses in pointers.  (This is not guaranteed to be the way pointers are implimented.)

>> int a[10];
>> int *p = a;
>> int *n;
>> n=p;
>> how is the array setup?
The array will be to integers in consecitive memory positions (maybe with padding between).  The two pointers, n and p, and the "psuedo-pointer" a will all point to the first (lowest memory) integer.
0
How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

 
LVL 2

Author Comment

by:Paullkha
Comment Utility
nietod
"However, the address of a ("&a") has a type of "int **" so it is compatible with z.  That is why the next line ..."

I read some ansi/c++ documentation on the web(very dry reading). It stated that there are implicit conversions occuring. Would you be able to elaborate on this?

int a[10];

int *p;
int (*z)[10];

p = a; implicit conversion to pointer OK. same as p = &a[0];
z= &a; what rules are being used to convert int[] (which a is) to int *[];
??? &&a[0] won't work - see where I am leading?

 
0
 
LVL 14

Expert Comment

by:AlexVirochovsky
Comment Utility
paullkha, i think that nietod and chinni comments are full, but if you
want, i can send you tutorial about pointers, that explains all about array, pointers to variables, pointers to functions and so on. If you really
want it, write you EMail.
 
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
in

int (*z)[10];

z is a pointer to something, that something is an array of 10 ints.  So it is no surprise that you can do

z = &a;

since a is an array of 10 ints and the & makes a pointer to it.  But

z = &&a[0]

does not work.  the a[0] is a single int,.  The second & gives you a pointer to a single int and the first & gives you a pointe to a pointer to an int.  So the two types are not the same.

I guess you're confussion mght be that the array can be in that an converted to a pointer, so you think z could be converted to the pointer to pointer to int.  No.  that conversion only happens "once".  i.e if someting is an array, it can be converted to a pointer to the thing that is arrayed.  But if the thing is an array of arrays, it is not possible to convert it to a pointer to a pointer to the thing in the array.
0
 

Expert Comment

by:gashev
Comment Utility
Hi!

Example:

#include <iostream.h>
void main(void)
{
   int a[10]={2,4,8,16,32,64,0};
   int* p;
   p=a;
   cout<<p[5];

}

p[5]=64;

Code work.
But I don't undestand line2:int *p=0;If you want to define p, use operator new and delete.
int* p;
p=new int;
*p=0;
.........
delete p;
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
paullkha, what is happening with this question?  

gashev, are you asking a question or explaining something?
0
 
LVL 2

Author Comment

by:Paullkha
Comment Utility
I am studying some documentation from Alex. This seems to be what I needed.
0
 
LVL 2

Author Comment

by:Paullkha
Comment Utility
-Alex
The docs helped alot.
Last questions:

int a[4] = {1,2,3,4}; //array address is 2000

int (*z)[4];
int *p;

p = a;
z = &a;
======
Below from debugger
p = 2000
z = 2000
*p = 1
*z = 2000
(*z)[0] = 1

How can z=&a compile if a already represents an address?(address of an address?)
Derefencing z leads back to the value of z? I understand that *z = a but what is occuring at compile time?

0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
>> How can z=&a compile if a already
>> represents an address?
"a" "represents" two things.  it represents a pointer (address) to a single int or it represents an array of 4 ints.

"z" is a pointer to an array of 4 ints.  So if you interpret "a" as a pointer to one int, then

z =a

doesn't compile because z needs a pointer to 4 ints.  But if you interpret "a" as a an array of 4 ints, then

z  = &a

works because it returns a pointer to an array of 4 ints.  Does that make sense
0
 
LVL 22

Accepted Solution

by:
nietod earned 75 total points
Comment Utility
>> Derefencing z leads back to the value of z?
No.  it returns an array of 4 ints, the array that z is set to point to--assumign it is initialized before beind dereferenced.

>> I understand that *z = a but what is occuring at compile time?
Z is simply a pointer, and address.  The type of the pointer is that it points to an array of 4 ints.  Bear in mind that in C (and assembly) a pointer of type T can point to a single T or an array of any number of T's.  Now in this case, T is not "int", it is "int[4]"  i.e. z can point to an array of 4 ints or an array of arrays of 4 ints, like

int x[2][4] = {{1,2,3,4} ,
                      {5,6,7,8}};

so z can point to a (&a) or z can point to an item in x (&x[0]) or z can point to x itself, like

z = x;

does that help?
0
 

Expert Comment

by:gashev
Comment Utility
Wrox Press C++ Tutorial


--------------------------------------------------------------------------------

Indirect Data Access
The variables that we have dealt with so far provide you with the ability to name a memory location in which you can store data of a particular type. The contents of a variable are either entered from an external source, such as the keyboard, or calculated from other values that are entered. There is another kind of variable in C++ which does not store data that you normally enter or calculate, but greatly extends the power and flexibility of your programs. This kind of variable is called a pointer.

What is a Pointer?
Each memory location that you use to store a data value has an address. The address provides the means for your PC hardware to reference a particular data item. A pointer is a variable that stores an address of another variable of a particular type. A pointer has a variable name just like any other variable and also has a type which designates what kind of variables its contents refer to. Note that the type of a pointer variable includes the fact that it's a pointer. A variable that is a pointer which can contain addresses of locations in memory containing values of type int, is of type 'pointer to int'.

Declaring Pointers
The declaration for a pointer is similar to that of an ordinary variable, except that the pointer name has an asterisk in front of it to indicate that it's a variable which is a pointer. For example, to declare a pointer pnumber that points to a variable of type long, you could use the following statement:

long* pnumber;

This declaration has been written with the asterisk close to the type name. If you wish, you can also write it as:

long *pnumber;

The compiler won't mind at all. However, remember that the type of the variable pnumber is 'pointer to long', which is often indicated by placing the asterisk close to the type name.

You can mix declarations of ordinary variables and pointers in the same statement. For example:

long* pnumber, number = 99;

This declares the pointer pnumber of type 'pointer to long' as before, and also declares the variable number, of type long. On balance, it's probably better to declare pointers separately from other variables, otherwise the statement can appear misleading as to the type of the variables declared, particularly if you prefer to place the * adjacent to the type name. The following statements certainly look clearer and putting declarations on separate lines enables you to add comments for them individually, making for a program that is easier to read:

// Declaration and initialization of long variable
long number = 99;
// Declaration of variable of type pointer to long
long* pnumber;

It's a common convention in C++ to use variable names beginning with p to denote pointers. This makes it easier to see which variables in a program are pointers, which in turn can make a program easier to follow.

Let's take an example to see how this works, without worrying about what it's for. We will come on to how this is used very shortly. Suppose we have the long integer variable number, as we declared it above containing the value 99. We also have the pointer, pnumber, of type pointer to long, which we could use to store the address of our variable number. But how can we obtain the address of a variable?

The Address-Of Operator
What we need is the address-of operator, &. This is a unary operator which obtains the address of a variable. It's also called the reference operator, for reasons we will discuss later in this chapter. To set up the pointer that we have just discussed, we could write this assignment statement:

pnumber = &number;            // Store address of number in pnumber

The result of this operation is illustrated below:

You can use the operator & to obtain the address of any variable, but you need a pointer of the same type to store it. If you want to store the address of a double variable for example, the pointer must have been declared as double*, which is type 'pointer to double'.

Using Pointers
Taking the address of a variable and storing it in a pointer is all very well, but the really interesting aspect is how you can use it. Fundamental to using a pointer is accessing the data value in the variable to which a pointer points. This is done using the indirection operator, *.

The Indirection Operator
The indirection operator, *, is used with a pointer to access the contents of the variable to which it points. The name 'indirection operator' stems from the fact that the data is accessed indirectly. It is also called the de-reference operator, and the process of accessing the data in the variable pointed to by a pointer is termed de-referencing the pointer.

One aspect of this operator that can seem confusing is the fact that we now have several different uses for the same symbol, *. It is the multiply operator, the indirection operator, and it is used in the declaration of a pointer. Each time you use *, the compiler is able to distinguish its meaning by the context. When you multiply two variables, A*B for instance, then there's no meaningful interpretation of this expression for anything other than a multiply operation.

Why Use Pointers?
A question that usually springs to mind at this point is, 'Why use pointers at all?' After all, taking the address of a variable you already know and sticking it in a pointer so that you can de-reference it seems like an overhead you can do without. There a several reasons why pointers are important.

First of all, as you will see shortly, you can use pointer notation to operate on data stored in an array, which often executes faster than if you use array notation. Secondly, when we get to define our own functions later in the book, you will see that pointers are used extensively for enabling access to large blocks of data, such as arrays within a function, that are defined outside. Thirdly and most importantly, you will also see later that you can allocate space for variables dynamically, that is, during program execution. This sort of capability allows your program to adjust its use of memory depending on the input to the program. Since you don't know in advance how many variables you are going to create dynamically, the only way you can do this is by using pointers - so make sure you get the hang of this bit.

Try It Out - Using Pointers
We can try out various aspects of pointer operations with an example:

//Ex3_05.cpp
// Exercising pointers
#include <iostream>
using namespace std;

int main()
{
   long* pnumber = NULL; // Pointer declaration & initialization
   long number1 = 55, number2 = 99;
   pnumber = &number1;   // Store address in pointer
   *pnumber += 11;       // Increment number1 by 11

   cout << endl
        << "number1 = " << number1
        << "   &number1 = " << hex << pnumber;

   pnumber = &number2;   // Change pointer to address of number2
   number1 = *pnumber*10; // 10 times number2

   cout << endl
        << "number1 = " << dec << number1
        << "   pnumber = " << hex << pnumber
        << "   *pnumber = " << dec << *pnumber;

   cout << endl;
   return 0;
}

On my computer, this example generates the following output:

How It Works
There is no input to this example. All operations are carried out with the initializing values for the variables. After storing the address of number1 in the pointer pnumber, the value of number1 is incremented indirectly through the pointer in this statement:

*pnumber += 11;                // Increment number1 by 11

Note that when we first declared the pointer pnumber, we initialized it to NULL. We'll discuss pointer initialization in the next section.

The indirection operator determines that we are adding 11 to the contents of the variable pointed to, that is, number1. If we forgot the *, we would be attempting to add 11 to the address stored in the pointer.

The values of number1 and the address of number1, stored in pnumber, are displayed. We use the hex manipulator to generate the address output in hexadecimal notation.

You can obtain the value of ordinary integer variables as hexadecimal output by using the manipulator hex. You send it to the output stream in the same way that we have applied endl, with the result that all following output will be in hexadecimal notation. If you want the following output to be decimal, you need to use the manipulator dec in the next output statement to switch the output back to decimal mode again.

After the first line of output, the contents of pnumber are set to the address of number2. The variable number1 is then changed to the value of 10 times number2:

number1 = *pnumber*10;                // 10 times number2

This is calculated by accessing the contents of number2 indirectly through the pointer. The second line of output shows the results of these calculations

The address values you see in your output may well be different from those shown in the screenshot above, since they reflect where the program is loaded in memory, which depends on how your operating system is configured. The 0x prefixing the address values indicates that they are hexadecimal numbers. Note that the addresses &number1 and pnumber (when it contains &number2) differ by four bytes. This shows that number1 and number2 occupy adjacent memory locations, as a long variable requires four bytes. The output demonstrates that everything is working as we expect.

Initializing Pointers
Just as with arrays, using pointers that aren't initialized is extremely hazardous. If you do this, you can overwrite random areas of memory. The resulting damage just depends on how unlucky you are, so it's more than just a good idea to initialize your pointers. It's very easy to initialize a pointer to the address of a variable that has already been defined. Here you can see that we have initialized the pointer pnumber with the address of the variable number just by using the operator & with the variable name:

int number = 0;                  // Initialized integer variable
int* pnumber = &number;          // Initialized pointer

When initializing a pointer with another variable, remember that the variable must already have been declared prior to the pointer declaration.

Of course, you may not want to initialize a pointer with the address of a specific variable when you declare it. In this case, you can initialize it with the pointer equivalent of zero. For this, Visual C++ provides the symbol NULL that is already defined as 0, so you can declare and initialize a pointer using the following statement, rather like we did in the last example:

int* pnumber = NULL;        // Pointer not pointing to anything

This ensures that the pointer doesn't contain an address that will be accepted as valid, and provides the pointer with a value that you can check in an if statement, such as:

if(pnumber == NULL)
   cout << endl << "pnumber is null.";

Of course, you can also initialize a pointer explicitly with 0, which will also ensure that it is assigned a value that doesn't point to anything. In spite of it being arguably somewhat less legible, if you expect to run your code with other compilers, it is preferable to use 0 as an initializing value for a pointer that you want to be null.

This is also more consistent with the current 'good practice' in C++, the argument being that if you have an object with a name in C++, it should have a type. However, NULL does not have a type - it's an alias for 0.

To use 0 as the initializing value for a pointer you would simply write:

int* pnumber = 0;                // Pointer not pointing to anything

To check whether a pointer contains a valid address, you could use the statement:

if(pnumber == NULL)              // or pnumber == 0
   cout << endl << "pnumber is null.";

Equally well, you could use the statement,

if(!pnumber)
   cout << endl << "pnumber is null.";

which does exactly the same as the previous example.

Of course, you can also use the form:

if(pnumber!= 0)
   // Pointer is valid, so do something useful

The address pointed to by the NULL pointer contains a junk value. You should never attempt to de-reference a null pointer.

Pointers to char
A pointer of type char* has the interesting property - it can be initialized with a string constant. For example, we can declare and initialize such a pointer with the statement:

char* proverb = "A miss is as good as a mile.";

This looks very similar to initializing a char array but it's slightly different. This will create the string constant (actually a constant array of type char) with the character string appearing between the quotes and terminated with /0, and store the address of the constant in the pointer proverb. This is shown in the figure below:



Try It Out - Lucky Stars With Pointers
We could rewrite our lucky stars example using pointers instead of an array to see how that would work:

// Ex3_06.cpp
// Initializing pointers with strings
#include <iostream>
using namespace std;

int main()
{
   char* pstr1 = "Robert Redford";
   char* pstr2 = "Hopalong Cassidy";
   char* pstr3 = "Lassie";
   char* pstr4 = "Slim Pickens";
   char* pstr5 = "Boris Karloff";
   char* pstr6 = "Oliver Hardy";
   char* pstr  = "Your lucky star is ";
   int dice = 0;

   cout << endl
        << " Pick a lucky star!"
        << " Enter a number between 1 and 6: ";
   cin >> dice;
   cout << endl;

   switch(dice)
   {
      case 1: cout << pstr << pstr1;
              break;
      case 2: cout << pstr << pstr2;
              break;
      case 3: cout << pstr << pstr3;
              break;
      case 4: cout << pstr << pstr4;
              break;
      case 5: cout << pstr << pstr5;
              break;
      case 6: cout << pstr << pstr6;
              break;
      default: cout << "Sorry, you haven't got a lucky star.";
   }

   cout << endl;
   return 0;
}

How It Works
The array in Ex3_04.cpp has been replaced by the six pointers, pstr1 to pstr6, each initialized with a name. We have also declared an additional pointer, pstr, initialized with the phrase that we want to use at the start of a normal output line. Because we have discrete pointers, it is easier to use a switch statement to select the appropriate output message than to use an if as we did in the original version. Any incorrect values that are entered are all taken care of by the default option of the switch.

Outputting the string pointed to by a pointer couldn't be easier. As you can see, you simply write the pointer name. It may cross your mind at this point that in Ex3_05.cpp we wrote a pointer name in the output statement and the address that it contained was displayed. Why is it different here? The answer lies in the way the output operation views a pointer of type 'pointer to char'. It treats a pointer of this type as a string (which is an array of char), and so outputs the string itself, rather than its address.

Using pointers has eliminated the waste of memory that occurred with the array version of this program, but the program seems a little long-winded now - there must be a better way. Indeed there is - using an array of pointers.

Try It Out - Arrays of Pointers
With an array of pointers of type char, each element can point to an independent string, and the lengths of each of the strings can be different. We can declare an array of pointers in the same way that we declare a normal array. Let's go straight to rewriting the previous example using a pointer array:

// Ex3_07.cpp
// Initializing pointers with strings
#include <iostream>
using namespace std;

int main()
{
   // Initializing a pointer array
   char* pstr[] =  { "Robert Redford",
                     "Hopalong Cassidy",
                     "Lassie",
                     "Slim Pickens",
                     "Boris Karloff",
                     "Oliver Hardy"
                   };

   char* pstart = "Your lucky star is ";
   int dice = 0;

   cout << endl
        << " Pick a lucky star!"
        << " Enter a number between 1 and 6: ";
   cin >> dice;
   cout << endl;

   if(dice >= 1 && dice <= 6)           // Check input validity
      cout << pstart << pstr[dice-1];   // Output star name
   else
      // Invalid input
      cout << "Sorry, you haven't got a lucky star.";

   cout << endl;
   return 0;
}

How It Works
In this case, we are nearly getting the best of all possible worlds. We have a one-dimensional array of char pointers declared such that the compiler works out what the dimension should be from the number of initializing strings. The memory usage that results from this is illustrated below:



Compared to using a 'normal' array, the pointer array carries less overhead in terms of space. With an array, we would need to make each row the length of the longest string, and six rows of seventeen bytes each is 102 bytes, so by using a pointer array we have saved a whole... -1 bytes! What's gone wrong? The simple truth is that for this small number of relatively short strings, the size of the extra array of pointers is significant. You would make savings if you were dealing with more strings that were longer and had more variable lengths.

Space saving isn't the only advantage that you get by using pointers. In a lot of circumstances you save time too. Think of what happens if you want to move 'Oliver Hardy' to the first position and 'Robert Redford' to the end. With the pointer array as above you just need to swap the pointers - the strings themselves stay where they are. If we had stored these simply as strings, as we did in Ex3_04.cpp, a great deal of copying would be necessary - we would need to copy the whole string 'Robert Redford' to a temporary location while we copied 'Oliver Hardy' in its place, and then we would need to copy 'Robert Redford' to the end position. This would require significantly more computer time to execute.

Since we are using pstr as the array name, the variable holding the start of the output message needs to be different, so we have called it pstart. We select the string that we want to output by means of a very simple if statement, similar to that in the original version of the example. We either display a star selection or a suitable message if the user enters an invalid value.

One weakness of the way we have written the program is that the code assumes there are six options, even though the compiler is allocating the space for the pointer array from the number of initializing strings that we supply. So if we add a string to the list, we have to alter other parts of the program to take account of this. It would be nice to be able to add strings and have the program automatically adapt to however many strings there are.

The sizeof Operator
A new operator will help us here. The sizeof operator produces an integer constant that gives the number of bytes occupied by its operand. For example, with the variable dice from the previous example, the statement,

cout << sizeof dice;

will output the value 4, since dice was declared as int and therefore occupies 4 bytes.

The sizeof operator can be applied to an element in an array or to the whole array. When the operator is applied to an array name by itself, it produces the number of bytes occupied by the whole array, whereas when it is applied to a single element with the appropriate index value or values, it results in the number of bytes occupied by that element. Thus, in the last example, we could output the number of elements in the pstr array with the expression:

cout << (sizeof pstr)/(sizeof pstr[0]);

The expression divides the number of bytes occupied by the whole pointer array by the number of bytes occupied by the first element of the array. Since all elements of the array occupy the same amount of memory, the result is the number of elements in the array.

Remember that pstr is an array of pointers - using the sizeof operator on the array or on individual elements will not tell us anything about the memory occupied by the text strings.

The sizeof operator can also be applied to a type name rather than a variable, in which case the result is the number of bytes occupied by a variable of that type. The type name should be enclosed between parentheses. For example, after executing the statement,

long_size = sizeof(long);

the variable long_size will have the value 4.

The result returned by sizeof is obviously an integer and you can always treat it as such. Its precise type is actually size_t, which is used for values measured in bytes. The type size_t, which will pop up in various contexts from time to time, is defined in various standard libraries in Visual C++ and is equivalent to unsigned int.

Try It Out - Using the sizeof Operator
We can use this to amend the last example so that it automatically adapts to an arbitrary number of string values from which to select:

// Ex3_08.cpp
// Flexible array management using sizeof
#include <iostream.h>
using namespace std;

int main()
{
   // Initializing a pointer array
   char* pstr[] = { "Robert Redford",
                    "Hopalong Cassidy",
                    "Lassie",
                    "Slim Pickens",
                    "Boris Karloff",
                    "Oliver Hardy"
                  };
   char* pstart = "Your lucky star is ";

   // Number of array elements
   int count = (sizeof pstr)/(sizeof pstr[0]);
   int dice = 0;
   cout << endl
        << " Pick a lucky star!"
        << " Enter a number between 1 and " << count << ": ";
   cin >> dice;
   cout << endl;

   if(dice >= 1 && dice <= count)       // Check input validity
      cout << pstart << pstr[dice-1];   // Output star name
   else
      // Invalid input
      cout << "Sorry, you haven't got a lucky star.";

   cout << endl;
   return 0;
}

How It Works
As you can see, the changes required in the example are very simple. We just calculate the number of elements in the pointer array pstr and store the result in count. Then, wherever the total number of elements in the array was referenced as 6, we just use the variable count. You could now just add a few more names to the list of lucky stars and everything affected in the program will be adjusted automatically.

Constant Pointers and Pointers to Constants
The array pstr in the last example is clearly not intended to be modified in the program, and nor are the strings being pointed to, nor the variable count. It would be a good idea to ensure that these didn't get modified in error in the program. We could very easily protect the variable count from accidental modification by writing this:

const int count = (sizeof pstr)/(sizeof pstr[0]);

However, the array of pointers deserves closer examination. We declared the array like this:

// Initializing a pointer array
   char* pstr[] = { "Robert Redford",
                    "Hopalong Cassidy",
                    "Lassie",
                    "Slim Pickens",
                    "Boris Karloff",
                    "Oliver Hardy"
                  };

Each pointer in the array is initialized with the address of a string literal, "Robert Redford", "Hopalong Cassidy" and so on. VC6 adheres to the ANSI standard, and says that the type of a string literal is 'array of const char'. The reason the compiler allows you to assign a string literal to an array of char* rather than of const char* is for reasons of backwards compatibility with existing code. If you try to alter the character array with a statement like this,

*pstr[0] = 'X';

then the program will crash when you try to run it.

This was not the case with VC5, where the type of a string literal was 'array of char', and the line shown above would have happily let you re-christen Mr. Redford 'Xobert'.

We don't really want to have unexpected behavior like the program crashing at run time, and we can prevent it. A far better way of writing the declaration is as follows:

// Array of pointers to constants
   const char* pstr[] = { "Robert Redford",
                          "Hopalong Cassidy",
                          "Lassie",
                          "Slim Pickens",
                          "Boris Karloff",
                          "Oliver Hardy"
                        };

In this case, there is no ambiguity about the const-ness of the objects pointed to by the elements of the pointer array. If you now attempt to change these objects, the compiler will flag this as an error at compile time.

However, we could still legally write this statement:

pstr[0] = pstr[1];

Those lucky individuals due to be awarded Mr. Redford would get Mr. Cassidy instead, since both pointers now point to the same name. Note that this isn't changing the values of the objects pointed to by the pointer array element - it is changing the value of the pointer stored in pstr[0]. We should therefore inhibit this kind of change as well, since some people may reckon that good old Hoppy does not have the same sex appeal as Robert. We can do this with the following statement:

// Array of constant pointers to constants
   const char* const pstr[] = { "Robert Redford",
                                "Hopalong Cassidy",
                                "Lassie",
                                "Slim Pickens",
                                "Boris Karloff",
                                "Oliver Hardy"
                              };

To summarize, we can distinguish three situations relating to const, pointers and the objects to which they point:

A pointer to a constant object


A constant pointer to an object


A constant pointer to a constant object
In the first situation, the object pointed to cannot be modified, but we can set the pointer to point to something else:

const char* pstring = "Some text";

In the second, the address stored in the pointer can't be changed, but the object pointed to can be:

char* const pstring = "Some text";

Finally, in the third situation, both the pointer and the object pointed to have been defined as constant and, therefore, neither can be changed:

const char* const pstring = "Some text";

Of course, all this applies to pointers of any type. Type char is used here purely for illustrative purposes.

Pointers and Arrays

Array names can behave like pointers under some circumstances. In most situations, if you use the name of a one-dimensional array by itself, it is automatically converted to a pointer to the first element of the array. Note that this is not the case when the array name is used as the operand of the sizeof operator.

If we have these declarations,

double* pdata;
double data[5];

then we can write this assignment:

pdata = data;       // Initialize pointer with the array address

This statement assigns the address of the first element of the array data to the pointer pdata. Using the array name by itself refers to the address of the array, whereas the array name, data, with an index value refers to the contents of the element corresponding to that index value. So, if we want to store the address of that element in the pointer, then we have to use the address-of operator:

pdata = &data[1];

Here, the pointer pdata will contain the address of the second element of the array.

Pointer Arithmetic

You can perform arithmetic operations with pointers. You are limited to addition and subtraction in terms of arithmetic, but you can also perform comparisons using pointers to produce a logical result. Arithmetic with a pointer implicitly assumes that the pointer points to an array, and that the arithmetic operation is on the address contained in the pointer. For the pointer pdata, for example, we could assign the address of the third element of the array data to a pointer with this statement:

pdata = &data[2];

In this case, the expression pdata+1 would refer to the address of data[3], the fourth element of the data array, so we could make the pointer point to this element by writing this statement:

pdata += 1;          // Increment pdata to the next element

This increments the address contained in pdata by the number of bytes occupied by one element of the array, data. In general, the expression pdata+n, where n can be any expression resulting in an integer, will add n*sizeof(double) to the address contained in the pointer pdata, since it was declared to be of type pointer to double. This is illustrated below:



In other words, incrementing or decrementing a pointer works in terms of the type of the object pointed to. Increasing a pointer to long by one changes its contents to the next long address, and so increments the address by four. Similarly, incrementing a pointer to short by one will increment the address by two. The more common notation for incrementing a pointer is using the increment operator. For example, this,

pdata++;            // Increment pdata to the next element

is equivalent to (and more common than) the += form. However, the += form was used above to make it clear that while the increment value is actually specified as one, the effect is usually an increment greater than one, except in the case of a pointer to char.

The address resulting from an arithmetic operation on a pointer can be a value ranging from the address of the first element of the array to the address which is one beyond the last element. Outside of these limits, the behavior of the pointer is undefined.

You can, of course, de-reference a pointer on which you have performed arithmetic (there wouldn't be much point to it otherwise). For example, assuming that pdata is still pointing to data[2], then this statement,

*(pdata+1) = *(pdata+2);

is equivalent to this:

data[3] = data[4];

When you want to de-reference a pointer after incrementing the address it contains, the parentheses are necessary as the precedence of the indirection operator is higher than that of the arithmetic operators, + or -. If you write the expression *pdata+1, instead of *(pdata+1), this would add one to the value stored at the address contained in pdata, which is equivalent to executing data[2]+1. Since this isn't an lvalue, its use in the assignment statement above would cause the compiler to generate an error message.

We can use an array name as though it were a pointer for addressing elements of an array. If we have the same one-dimensional array as before, declared as

long data[5];

then using pointer notation, we can refer to the element data[3] for example as *(data+3). This kind of notation can be applied generally so that, corresponding to the elements data[0], data[1], data[2], we can write *data, *(data+1), *(data+2), and so on.

Try It Out - Array Names as Pointers

We could exercise this aspect of array addressing with a program to calculate prime numbers (a prime number is a number divisible only by itself and one).

// Ex3_09.cpp
// Calculating primes
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
   const int MAX = 100;        // Number of primes required
   long primes[MAX] = { 2,3,5 };  // First three primes defined
   long trial = 5;             // Candidate prime
   int count = 3;              // Count of primes found
   int found = 0;              // Indicates when a prime is found

   do
   {
      trial += 2;                    // Next value for checking
      found = 0;                     // Set found indicator

      // Try division by existing primes
      for(int i = 0; i < count; i++)
      {
         found = (trial % *(primes+i)) == 0; // True for exact division                                                      
         if(found)               // If division is exact
            break;               // it's not a prime
      }

      if (found == 0)        // We got one... so save it in
         *(primes + count++) = trial;   // ... primes array
   }while(count < MAX);

   // Output primes 5 to a line
   for(int i = 0; i < MAX; i++)
   {
      if(i%5 == 0)      // New line on 1st, and every 5th line
         cout << endl;
      cout << setw(10) << *(primes+i);
   }

   cout << endl;
   return 0;
}

If you compile and execute this example you should get the output shown:



How It Works

We have the usual #include statements for iostream for input and output, and for iomanip since we will be using a stream manipulator to set the field width for output.

We use the constant MAX to define the number of primes that we want the program to produce. The primes array, which stores the results, has the first three primes already defined to start the process off. All the work is done in two loops, the outer do-while loop, which picks the next value to be checked and adds the value to the primes array if it is prime, and the inner for loop that actually checks the value to see whether it's prime or not.

The algorithm in the for loop is very simple and is based on the fact that if a number is not a prime, then it must be divisible by one of the primes found so far - all of which are less than the number in question, since all numbers are either prime or a product of primes. In fact, only division by primes less than or equal to the square root of the number in question need to be checked, so this example isn't as efficient as it might be.

found = (trial % *(primes+i)) == 0; // True for exact division
This statement sets the variable found to be 1 if there's no remainder from dividing the value in trial by the current prime, *(primes+i) (remember that this is equivalent to primes[i]), and 0 otherwise. The if statement causes the for loop to be terminated if found has the value 1, since the candidate in trial can't be a prime in that case.

After the for loop ends (for whatever reason), it's necessary to decide whether or not the value in trial was prime. This is indicated by the value in the indicator variable found.

*(primes + count++) = trial;   //    primes array
If trial does contain a prime, this statement stores the value in primes[count] and then increments count through the postfix increment operator.

Once MAX number of primes have been found, they are output with a field width of 10 characters, 5 to a line, as a result of this statement:


if(i%5 == 0)      // New line on 1st, and every 5th line
   cout << endl;
This starts a new line when i has the values 0, 5, 10, and so on.

Try It Out - Counting Characters Revisited

To see how handling strings works in pointer notation, we could produce a version of the program we looked at earlier for counting the characters in a string:

// Ex3_10.cpp
// Counting string characters using a pointer
#include <iostream>
using namespace std;

int main()
{
   const int MAX = 80;                 // Maximum array dimension
   char buffer[MAX];                   // Input buffer
   char* pbuffer=buffer;               // Pointer to array buffer
   cout << endl                        // Prompt for input
        << "Enter a string of less than "
        << MAX << " characters:"
        << endl;
   cin.getline(buffer, MAX, '\n');     // Read a string until \n

   while(*pbuffer)                     // Continue until \0
      pbuffer++;

   cout << endl
        << "The string \"" << buffer
        << "\" has " << pbuffer-buffer << " characters.";

   cout << endl;
   return 0;
}

How It Works

Here the program operates using the pointer pbuffer rather than the array name buffer. We don't need the count variable, since the pointer is incremented in the while loop until \0 is found. When the \0 is found, pbuffer will contain the address of that position in the string. The count of the number of characters in the string entered is therefore the difference between the address stored in the pointer pbuffer and the address of the beginning of the array denoted by buffer.

We could also have incremented the pointer in the loop by writing the loop like this:

while(*pbuffer++);                   // Continue until \0

Now the loop contains no statements, only the test condition. This would work adequately, except for the fact that the pointer would be incremented after \0 was encountered, so the address would be one more than the last position in the string. We would therefore need to express the count of the number of characters in the string as pbuffer-buffer-1.

Note that here we can't use the array name in the same way that we have used the pointer. The expression buffer++ is strictly illegal since you can't modify an array name - it isn't a pointer.

Using Pointers with Multidimensional Arrays

Using a pointer to store the address of a one-dimensional array is relatively straightforward, but with multidimensional arrays, things can get a little complicated. If you don't intend to do this, you can skip this section as it's a little obscure, but if your previous experience is with C, this section is worth a glance.

If you have to use a pointer with multidimensional arrays, you need to keep clear in your mind what is happening. By way of illustration, we can use an array beans, declared as follows:

double beans[3][4];

We can declare and assign a value to the pointer pbeans as follows:

double* pbeans;
pbeans = &beans[0][0];

Here we are setting the pointer to the address of the first element of the array, which is of type double. We could also set the pointer to the address of the first row in the array with the statement:

pbeans = beans[0];

This is equivalent to using the name of a one-dimensional array which is replaced by its address. We used this in the earlier discussion. However, because beans is a two-dimensional array, we cannot set an address in the pointer with the following statement:

pbeans = beans;           // Will cause an error!!

The problem is one of type. The type of the pointer you have defined is double*, but the array is of type double[3][4]. A pointer to store the address of this array must be of type double*[4]. C++ associates the dimensions of the array with its type and the statement above is only legal if the pointer has been declared with the dimension required. This is done with a slightly more complicated notation than we have seen so far:

double (*pbeans)[4];

The parentheses here are essential, otherwise you would be declaring an array of pointers. Now the previous statement is legal, but this pointer can only be used to store addresses of an array with the dimensions shown.

Pointer Notation with Multidimensional Arrays

You can use pointer notation with an array name to reference elements of the array. You can reference each element of the array beans that we declared earlier in two ways:

Using the array name with two index values.


Using the array name in pointer notation
Therefore, the following two statements are equivalent:

beans[i][j]
*(*(beans+i)+j)

Let's look at how these work. The first line uses normal array indexing to refer to the element with offset j in row i of the array.

We can determine the meaning of the second line by working outwards from the inside. beans refers to the address of the first row of the array, so beans+i refers to row i of the array. The expression *(beans+i) is the address of the first element of row i, so *(beans+i)+j is the address of the element in row i with offset j. The whole expression therefore refers to the value of that element.

If you really want to be obscure - and it isn't recommended that you do so - the following two statements, where we have mixed array and pointer notation, are also legal references to the same element of the array:

*(beans[i]+j)
(*(beans+i))[j]

There is yet another aspect to the use of pointers which is really the most important of all: the ability to create variables dynamically. We will look into that next.


--------------------------------------------------------------------------------
) 1998 Wrox Press
0
 

Expert Comment

by:gashev
Comment Utility
Paullkha don't know what is it pointer.
0
 
LVL 22

Expert Comment

by:nietod
Comment Utility
actually, Paulkha has a background in assembly, so he/she probably has a very good idea what a pointer is.  But in C/C++ poiinters and arrays are more restricted than in assembly.  The question is more about what is legal, not what a pointer is.
0

Featured Post

How your wiki can always stay up-to-date

Quip doubles as a “living” wiki and a project management tool that evolves with your organization. As you finish projects in Quip, the work remains, easily accessible to all team members, new and old.
- Increase transparency
- Onboard new hires faster
- Access from mobile/offline

Join & Write a Comment

Article by: SunnyDark
This article's goal is to present you with an easy to use XML wrapper for C++ and also present some interesting techniques that you might use with MS C++. The reason I built this class is to ease the pain of using XML files with C++, since there is…
Jaspersoft Studio is a plugin for Eclipse that lets you create reports from a datasource.  In this article, we'll go over creating a report from a default template and setting up a datasource that connects to your database.
The viewer will learn how to clear a vector as well as how to detect empty vectors in C++.
The viewer will be introduced to the technique of using vectors in C++. The video will cover how to define a vector, store values in the vector and retrieve data from the values stored in the vector.

744 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

12 Experts available now in Live!

Get 1:1 Help Now