Here, we review how to use basic pointers and arrays. We do not describe everything one might want to do with pointers or arrays.
The complete code we use in this section is available as ex1.c.
Suppose a main() function (or any other function) defines
the following variables:
int a = 8, b = -2; int *ip;
These would look something like the following in memory:
a @1000 b @2000 ------ ------ | 8 | | -2 | ------ ------ ip @3000 ------ | | ------
I.e., each variable is located at a certain point in memory (e.g.,
suppose a is at location 1000, b
at location 2000, and ip at location
3000).
The variables a and b have initial values of
8 and -2 respectively. The pointer ip has an arbitrary
initial value.
Now, what would each of the following statements do:
ip = &a;
This would make ip point to a. Graphically,
this means we'd have:
a @1000 b @2000 ------ ------ +->| 8 | | -2 | | ------ ------ | | ip @3000 | ------ +--+-- | ------
Another way to say this is that ip now holds the address of
a (the ampersand (&) operator is what we used
to get the address of a). So, we can also think of this
statement as doing:
a @1000 b @2000 ------ ------ | 8 | | -2 | ------ ------ ip @3000 ------ |1000| ------
*ip = 7;
Using the dereference operator (*) on a pointer gives you
the thing it points to. Since ip currently points to
a, the above statement effectively assigns the value 7 to
a:
a @1000 b @2000 ------ ------ | 7 | | -2 | ------ ------ ip @3000 ------ |1000| ------
ip = 5;
Unlike the previous statement, we have not used dereferencing. Thus, this is an attempt to give an integer value to a pointer. The compiler should at least warn us that the types are not compatible, since we should not do this.
Now, suppose we have two other functions in this program:
void foo(int c)
{
c += 5;
}
void bar(int *jp)
{
*jp += 8;
}
What would happen when we performed each of the following statements in
our main() function:
foo(a);
Function foo() receives its integer parameter through
a variable named c, so a new variable comes into existence
and holds the value passed to it:
| Scope of main(): | Scope of foo(): | |
|---|---|---|
a @1000 b @2000 ------ ------ | 7 | | -2 | ------ ------ ip @3000 ------ |1000| ------ |
c @4000 ------ | 7 | ------ |
(Imagine that this new variable is at memory location 4000.)
Now, when function foo() performs:
c += 5;
it only affects its parameter c:
| Scope of main(): | Scope of foo(): | |
|---|---|---|
a @1000 b @2000 ------ ------ | 7 | | -2 | ------ ------ ip @3000 ------ |1000| ------ |
c @4000 ------ | 12 | ------ |
Finally, when the function foo() ends, its parameter
c will no longer exist.
bar(&a);
Passes a to bar() by reference. So, the
parameter jp in bar() will hold the address
of a:
| Scope of main(): | Scope of bar(): | |
|---|---|---|
a @1000 b @2000 ------ ------ | 7 | | -2 | ------ ------ ip @3000 ------ |1000| ------ |
jp @5000 ------ |1000| ------ |
(Suppose that jp is at memory location 5000.)
Now, the statement in bar() that does:
*jp += 8;
does not change jp, but rather, the thing jp
points to, i.e., a in main():
| Scope of main(): | Scope of bar(): | |
|---|---|---|
a @1000 b @2000 ------ ------ | 15 | | -2 | ------ ------ ip @3000 ------ |1000| ------ |
jp @5000 ------ |1000| ------ |
Again, when the function bar() ends, its parameter
jp will no longer exist.
bar(ip);
This calls the same function but with a pointer variable, ip.
Since ip currently points to a, and thus, so will
jp, the function again changes a:
| Scope of main(): | Scope of bar(): | |
|---|---|---|
a @1000 b @2000 ------ ------ | 23 | | -2 | ------ ------ ip @3000 ------ |1000| ------ |
jp @5000 ------ |1000| ------ |
ip = &b; bar(ip);
Since ip now points to b, the parameter
jp will receive b's address. Thus, the
function call will change b this time:
| Scope of main(): | Scope of bar(): | |
|---|---|---|
a @1000 b @2000 ------ ------ | 23 | | 6 | ------ ------ ip @3000 ------ |2000| ------ |
jp @5000 ------ |2000| ------ |
The complete code we use in this section is available as ex2.c.
Suppose a main() function (or any other function) defines
the following variables:
double ar[4] = { 1.2, 3.5, -2.0 };
double *dp;
These would look something like the following in memory:
ar @6000 --------------------- | 1.2| 3.5|-2.0| 0.0| --------------------- dp @7000 ------ | | ------
I.e., the array ar has room for 4 elements (as its size
indicated). Since only 3 initializers are given, the 4th position gets
initialized to zero. The pointer dp is meant to point to
the kind of things in the array, doubles.
Now, what would each of the following statements do:
ar[3] = 11.1;
Since ar is an array, we can use subscripts (in brackets,
[]) to access an element, and for example, change its
value. Now, we have:
ar @6000 --------------------- | 1.2| 3.5|-2.0|11.1| --------------------- dp @7000 ------ | | ------
dp = ar;
Up until now, the pointer dp pointed to some arbitrary
location. Now, we want it to point to ar. The above code
achieves this by storing the address of the beginning of the array in
dp.
ar @6000 --------------------- | 1.2| 3.5|-2.0|11.1| --------------------- dp @7000 ------ |6000| ------
Notice that since the name of an array behaves like the "address of
its first element" in certain contexts, we neither need or want to
use ampersand (&) to take ar's address,
i.e., we just did:
dp = ar;
ar = dp; /* Can't do! */
This is in fact illegal. Even though the name of an array behaves like the address of its first element in certain contexts, an array is not a pointer that can store different addresses. Thus, you cannot assign to the name of an array.
*dp = 5.7;
This assigns 5.7 to the first element of the array:
ar @6000 --------------------- | 5.7| 3.5|-2.0|11.1| --------------------- dp @7000 ------ |6000| ------
Again, using the dereference operator (*) on a pointer gives
you the thing it points to. Since dp currently points to
the first element of ar, the statement effectively assigns
the value 5.7 to that element.
dp[2] = 0.0;
This assigns 0.0 to the 3rd element of the array:
ar @6000 --------------------- | 5.7| 3.5| 0.0|11.1| --------------------- dp @7000 ------ |6000| ------
Once a pointer is set to the beginning of an array, like dp,
you can use that pointer to access the array in the same manner as you
can use the array's name (above, for example, we use a subscript).
Equivalently, we could have done:
*(dp + 2) = 0.0;
dp[2] and *(dp + 2) mean exactly
the same things. I.e., subscripting is just shorthand for pointer
arithmetic with dereferencing. For example,
p[3]
means to create a new, temporary pointer that is 3 elements
(not bytes) after where p points, then dereference that
temporary pointer.
Now, suppose we add a function to print out all the values in a
double array:
void PrintEm(double br[], int n)
{
int i;
for (i = 0; i < n; i++)
printf("%f\n", br[i]);
}
Some things to note about PrintEm():
doubles, so we declared
its first parameter as:
double br[]
The array size is not necessary inside of brackets. If we put a size, it won't even be used (we'll see why shortly).
n.
This is needed since there is no way to tell how big a passed
array is. We'll need this to know how many elements to print.
br[i], to access each element.
Now, to pass ar to PrintEm(), we'd do:
PrintEm(ar, 4);
Since ar is an array and the function wants an array, it
makes sense to just pass it along. When PrintEm() is
called, its parameter br will come into existence:
| Scope of main(): | Scope of PrintEm(): | |
|---|---|---|
ar @6000 --------------------- | 5.7| 3.5| 0.0|11.1| --------------------- dp @7000 ------ |6000| ------ |
br @8000 ------ |6000| ------ |
You'll note however that br is not an array like
ar. It's actually just a pointer, like dp.
(Since it's really just a pointer, that is why we didn't have to give
br an array size.)
This illustrates the different between using array notation to declare an array as a parameter versus as a local or global variable. When you do something like:
int array[10];
as a local variable (in a function) or as a global variable, you really get an array. When you use that notation inside of a parameter list:
void SomeFunc(int array[]);
you only get a pointer. I.e., arrays are always passed by reference, automatically.
So, it makes sense that we could just pass the array as:
PrintEm(ar, 4);
since the name of an array, like ar, acts like the address
of its first element in certain contexts...an address was exactly what
PrintEm() was expecting since its parameter
br really is just a pointer.
Now, let's perform some more statements:
PrintEm(dp, 4);
This does exactly the same thing as the above statement:
PrintEm(ar, 4);
since dp currently points to the beginning of ar.
(*dp)++
This increments the first element of the array:
ar @6000 --------------------- | 6.7| 3.5| 0.0|11.1| --------------------- dp @7000 ------ |6000| ------
You should read that statement as: "get what dp points to
and then increment it."
dp++;
This increments the pointer. I.e., it makes dp point to the
next (2nd) element in the array. If doubles are 8 bytes,
that would give:
ar @6000 --------------------- | 5.7| 3.5| 0.0|11.1| --------------------- dp @7000 ------ |6008| ------
Now, we said that the parameter br in PrintEm()
is really just a pointer. So, we can write a second version of this
function, PrintEm2() that makes that fact explicit:
void PrintEm2(double *br, int n)
{
int i;
for (i = 0; i < n; i++)
printf("%f\n", *br++);
}
We now explicitly list the parameter as a pointer. Also, instead of using subscripting, we use some pointer arithmetic.
How should you read *br++?
* (dereferencing) and ++
(here, the post-increment) have the same precedence. So, you must go
with associativity--they associate from right to left.
So, the first part to be done is "br++", which says to
increment the pointer. But wait, its a post-increment,
so we use it first... I.e., we use the variable to do "*br",
which means to use the double that br points to.
Finally, we can do the post-increment and advance the pointer.
So, the expression "*br++" really means:
br points to (i.e., print out that
double).
double).
In summary, we now only use i to figure out how many
elements to print. We use pointer arithmetic to advance from one
element to the next.
We can now call this new version, PrintEm2(), passing
along the array. Here are some statements that do that:
PrintEm2(dp, 3);
Prints the last 3 elements in the array. Recall that dp now
points to the 2nd element, so we skip the 1st one.
PrintEm2(ar, 4);
Prints out all 4 elements in the array.
PrintEm2(ar+1, 3);
Prints out the last 3 elements in the array. This time we use
ar to construct the address of its 2nd element (i.e., +1
elements past the first one). Equivalently, we could have done:
PrintEm2(&ar[1], 3);
since that is also a way to send the "address of the 2nd element".
The complete code we use in this section is available as ex3.c.
Although strings are stored in arrays, and thus, work like our array examples above, strings have a few special features:
\0), to represent the end of a string. Thus, we can
figure out how big the string stored in an array is.
strlen()) that
operate on strings of this form.
Now, suppose a main() function (or any other function) defines
the following variables:
char str[] = "cs113"; char *sp;
These would look something like the following in memory:
str @9000 -------------------------- | c | s | 1 | 1 | 3 | \0 | -------------------------- sp @10000 ------ | | ------
Note that the array has 6 elements, an extra one for the nul character.
Now, what would each of the following statements do:
str[4] = '1';
We can access an individual character with the array name and subscripting. Above, we changed the 5th character:
str @9000 -------------------------- | c | s | 1 | 1 | 1 | \0 | -------------------------- sp @10000 ------ | | ------
Remember that this array holds characters, so we can assign characters
(like '1') to its elements.
sp = str;
Up until now, the pointer sp pointed to some arbitrary
location. The above statement makes it point to str:
str @9000 -------------------------- | c | s | 1 | 1 | 1 | \0 | -------------------------- sp @10000 ------ |9000| ------
Again, no ampersand (&) is needed or desired.
str = sp; /* Can't do! */
Again, this is illegal since you can't move an array.
*sp = 'm';
This assigns 'm' to the 1st element of the array:
str @9000 -------------------------- | m | s | 1 | 1 | 1 | \0 | -------------------------- sp @10000 ------ |9000| ------
sp[1] = 'a';
This assigns 'a' to the 2nd element of the array:
str @9000 -------------------------- | m | a | 1 | 1 | 1 | \0 | -------------------------- sp @10000 ------ |9000| ------
Equivalently, we could have done:
*(sp + 1) = 'a';
Now, suppose we add a function to print out the string (of course,
printf() can do this already, but we'll do it ourselves
character by character):
void PrintStr(char astr[])
{
/* call strlen just once (more efficiency) */
int len = strlen(astr);
int i;
for (i = 0; i < len; i++)
printf("%c", astr[i]);
printf("\n");
}
Some things to note about PrintStr():
strlen() for the length of
the string (the length of "ma111" is 5). That's why we don't need a
2nd parameter, the size of the string.
astr[i]".
Now, to pass str to PrintStr(), we'd just do:
PrintStr(str);
When PrintStr() is called, its parameter astr
will come into existence:
| Scope of main(): | Scope of PrintStr(): | |
|---|---|---|
str @9000 -------------------------- | c | s | 1 | 1 | 3 | \0 | -------------------------- sp @10000 ------ |9000| ------ |
astr @11000 ------ |9000| ------ |
Again, astr is not an array like str, but
just a pointer.
Let's now write a second version of the printing function,
PrintStr2() that takes advantage of this pointer:
void PrintStr2(char *astr)
{
while (*astr != '\0')
printf("%c", *astr++);
printf("\n");
}
We've already used notation like "*astr+++" before.
Moreover, this new version no longer needs i,
len or strlen() to count until the end of the
string. Instead, we just advance a pointer from one character to the
next until we get to the nul character (\0).
We can now call this new version, PrintStr2(), passing
along the array. Here are some statements that do that:
PrintStr2(str);
Print out the whole string.
PrintStr2(&str[2]);
Prints out the "111" part of the string. We could equivalently write that as:
PrintStr2(str+2);
or even replace str with sp (since
sp still points to the beginning of that string).
So, pulling all that you have learned together, consider the following variable definitions:
int m; float fa[3]; char s[10];
How would you complete the following scanf() to read in:
a value for m, the 2nd element in fa, and a
string value for s?
scanf("%__%__%__", __________, ___________, ___________);