Warning: Declaration of action_plugin_subjectindex_indexer::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php on line 15
Warning: Declaration of action_plugin_mathjax_enable::register(Doku_Event_Handler &$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/mathjax/action/enable.php on line 62
Warning: Declaration of action_plugin_googleanalytics::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/googleanalytics/action.php on line 40
Warning: Declaration of action_plugin_folded::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/folded/action.php on line 40
Warning: Declaration of action_plugin_hidden::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/hidden/action.php on line 28
Warning: Declaration of action_plugin_include::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/include/action.php on line 354
Warning: Declaration of action_plugin_tag::register(&$contr) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/tag/action.php on line 175
Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/auth.php on line 532
Warning: preg_replace(): The /e modifier is no longer supported, use preg_replace_callback instead in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/auth.php on line 818
Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656
Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656
Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656
======Exercise 15: Pointers Dreaded Pointers======
Pointers are famous mystical creatures in C that I will attempt to
demystify by teaching you the vocabulary used to deal with them. They
actually aren't that complex, it's just they are frequently abused in
weird ways that make them hard to use. If you avoid the stupid ways to
use pointers then they're fairly easy.
To demonstrate pointers in a way we can talk about them, I've written a
frivolous program that prints a group of people's ages in three
different ways:
#include
int main(int argc, char *argv[])
{
// create two arrays we care about
int ages[] = {23, 43, 12, 89, 2};
char *names[] = {
"Alan", "Frank",
"Mary", "John", "Lisa"
};
// safely get the size of ages
int count = sizeof(ages) / sizeof(int);
int i = 0;
// first way using indexing
for(i = 0; i < count; i++) {
printf("%s has %d years alive.\n",
names[i], ages[i]);
}
printf("---\n");
// setup the pointers to the start of the arrays
int *cur_age = ages;
char **cur_name = names;
// second way using pointers
for(i = 0; i < count; i++) {
printf("%s is %d years old.\n",
*(cur_name+i), *(cur_age+i));
}
printf("---\n");
// third way, pointers are just arrays
for(i = 0; i < count; i++) {
printf("%s is %d years old again.\n",
cur_name[i], cur_age[i]);
}
printf("---\n");
// fourth way with pointers in a stupid complex way
for(cur_name = names, cur_age = ages;
(cur_age - ages) < count;
cur_name++, cur_age++)
{
printf("%s lived %d years so far.\n",
*cur_name, *cur_age);
}
return 0;
}
Before explaining how pointers work, let's break this program down
line-by-line so you get an idea of what's going on. As you go through
this detailed description, try to answer the questions for yourself on
a piece of paper, then see if what you guessed was going on matches my
description of pointers later.
ex15.c:6-10
Create two arrays, ages storing some int data, and names storing
an array of strings.
ex15.c:12-13
Some variables for our for-loops later.
ex15.c:16-19
You know this is just looping through the two arrays and
printing how old each person is. This is using i to index into
the array.
ex15.c:24
Create a pointer that points at ages. Notice the use of int * to
create a "pointer to integer" type of pointer. That's similar to
char *, which is a "pointer to char", and a string is an array
of chars. Seeing the similarity yet?
ex15.c:25
Create a pointer that points at names. A char * is already a
"pointer to char", so that's just a string. You however need 2
levels, since names is 2-dimensional, that means you need char
** for a "pointer to (a pointer to char)" type. Study that too,
explain it to yourself.
ex15.c:28-31
Loop through ages and names but instead use the pointers plus an
offset of i. Writing *(cur_name+i) is the same as writing
name[i], and you read it as "the value of (pointer cur_name plus
i)".
ex15.c:35-39
This shows how the syntax to access an element of an array is
the same for a pointer and an array.
ex15.c:44-50
Another admittedly insane loop that does the same thing as the
other two, but instead it uses various pointer arithmetic
methods:
ex15.c:44
Initialize our for-loop by setting cur_name and cur_age to
the beginning of the names and ages arrays.
ex15.c:45
The test portion of the for-loop then compares the
distance of the pointer cur_age from the start of ages.
Why does that work?
ex15.c:46
The increment part of the for-loop then increments both
cur_name and cur_age so that they point at the next
element of the name and age arrays.
ex15.c:48-49
The pointers cur_name and cur_age are now pointing at one
element of the arrays they work on, and we can print them
out using just *cur_name and *cur_age, which means "the
value of wherever cur_name is pointing".
This seemingly simple program has a large amount of information, and
the goal is to get you to attempt figuring pointers out for yourself
before I explain them. Don't continue until you've written down what
you think a pointer does.
======What You Should See======
After you run this program try to trace back each line printed out to
the line in the code that produced it. If you have to, alter the printf
calls to make sure you got the right line number.
$ make ex15
cc -Wall -g ex15.c -o ex15
$ ./ex15
======Alan has 23 years alive.======
======Frank has 43 years alive.======
======Mary has 12 years alive.======
======John has 89 years alive.======
======Lisa has 2 years alive.======
---
======Alan is 23 years old.======
======Frank is 43 years old.======
======Mary is 12 years old.======
======John is 89 years old.======
======Lisa is 2 years old.======
---
======Alan is 23 years old again.======
======Frank is 43 years old again.======
======Mary is 12 years old again.======
======John is 89 years old again.======
======Lisa is 2 years old again.======
---
======Alan lived 23 years so far.======
======Frank lived 43 years so far.======
======Mary lived 12 years so far.======
======John lived 89 years so far.======
======Lisa lived 2 years so far.======
$
======Explaining Pointers======
When you type something like ages[i] you are "indexing" into the array
ages, and you're using the number that's held in i to do it. If i is
set to 0 then it's the same as typing ages[0]. We've been calling this
number i an "index" since it's a location inside ages that we want. It
could also be called an "address", that's a way of saying "I want the
integer in ages that is at address i".
If i is an index, then what's ages? To C ages is a location in the
computer's memory where all of these integers start. It is also an
address, and the C compiler will replace anywhere you type ages with
the address of the very first integer in ages. Another way to think of
ages is it's the "address of the first integer in ages". But, the trick
is ages is an address inside the entire computer. It's not like i which
was just an address inside ages. The ages array name is actually an
address in the computer.
That leads to a certain realization: C thinks your whole computer is
one massive array of bytes. Obviously this isn't very useful, but then
C layers on top of this massive array of bytes the concept of types and
sizes of those types. You already saw how this worked in previous
exercises, but now you can start to get an idea that C is somehow doing
the following with your arrays:
* Creating a block of memory inside your computer.
* "Pointing" the name ages at the beginning of that block.
* "Indexing" into the block by taking the base address of ages and
getting the element that's i away from there.
* Converting that address at ages+i into a valid int of the right
size, such that the index works to return what you want: the int at
index i.
If you can take a base address, like ages, and then "add" to it with
another address like i to produce a new address, then can you just make
something that points right at this location all the time? Yes, and
that thing is called a "pointer". This is what the pointers cur_age and
cur_name are doing. They are variables pointing at the location where
ages and names live in your computer's memory. The example program is
then moving them around or doing math on them to get values out of the
memory. In one instance, they just add i to cur_age, which is the same
as what it does with array[i]. In the last for-loop though these two
pointers are being moved on their own, without i to help out. In that
loop, the pointers are treated like a combination of array and integer
offset rolled into one.
A pointer is simply an address pointing somewhere inside the computer's
memory, with a type specifier so you get the right size of data with
it. It is kind of like a combined ages and i rolled into one data type.
C knows where pointers are pointing, knows the data type they point at,
the size of those types, and how to get the data for you. Just like i
you can increment them, decrement them, subtract or add to them. But,
just like ages you can also get values out with them, put new values
in, and all the array operations.
The purpose of a pointer is to let you manually index into blocks or
memory when an array won't do it right. In almost all other cases you
actually want to use an array. But, there are times when you have to
work with a raw block of memory and that's where a pointer comes in. A
pointer gives you raw, direct access to a block of memory so you can
work with it.
The final thing to grasp at this stage is that you can use either
syntax for most array or pointer operations. You can take a pointer to
something, but use the array syntax for accessing it. You can take an
array and do pointer arithmetic with it.
======Practical Pointer Usage======
There are four primary useful things you do with pointers in C code:
* Ask the OS for a chunk of memory and use a pointer to work with it.
This includes strings and something you haven't seen yet, structs.
* Passing large blocks of memory (like large structs) to functions
with a pointer so you don't have to pass the whole thing to them.
* Taking the address of a function so you can use it as a dynamic
callback.
* Complex scanning of chunks of memory such as converting bytes off a
network socket into data structures or parsing files.
For nearly everything else you see people use pointers, they should be
using arrays. In the early days of C programming people used pointers
to speed up their programs because the compilers were really bad at
optimizing array usage. These days the syntax to access an array vs. a
pointer are translated into the same machine code and optimized the
same, so it's not as necessary. Instead, you go with arrays every time
you can, and then only use pointers as a performance optimization if
you absolutely have to.
======The Pointer Lexicon======
I'm now going to give you a little lexicon to use for reading and
writing pointers. Whenever you run into a complex pointer statement,
just refer to this and break it down bit by bit (or just don't use that
code since it's probably not good code):
type *ptr
"a pointer of type named ptr"
*ptr
"the value of whatever ptr is pointed at"
*(ptr + i)
"the value of (whatever ptr is pointed at plus i)"
&thing
"the address of thing"
type *ptr = &thing
"a pointer of type named ptr set to the address of thing"
ptr++
"increment where ptr points"
We'll be using this simple lexicon to break down all of the pointers we
use from now on in the book.
======Pointers Are Not Arrays======
No matter what, you should never think that pointers and arrays are the
same thing. They are not the same thing, even though C lets you work
with them in many of the same ways. For example, if you do
sizeof(cur_age) in the code above, you would get the size of the
pointer, not the size of what it points at. If you want the size of the
full array, you have to use the array's name, age as I did on line 12.
TODO: expand on this some more with what doesn't work on both the same.
======How To Break It======
You can break this program by simply pointing the pointers at the wrong
things:
* Try to make cur_age point at names. You'll need to use a C cast to
force it, so go look that up and try to figure it out.
* In the final for-loop try getting the math wrong in weird ways.
* Try rewriting the loops so they start at the end of the arrays and
go to the beginning. This is harder than it looks.
======Extra Credit======
* Rewrite all the array usage in this program so that it's pointers.
* Rewrite all the pointer usage so they're arrays.
* Go back to some of the other programs that use arrays and try to
use pointers instead.
* Process command line arguments using just pointers similar to how
you did names in this one.
* Play with combinations of getting the value of and the address of
things.
* Add another for-loop at the end that prints out the addresses these
pointers are using. You'll need the %p format for printf.
* Rewrite this program to use a function for each of the ways you're
printing out things. Try to pass pointers to these functions so
they work on the data. Remember you can declare a function to
accept a pointer, but just use it like an array.
* Change the for-loops to while-loops and see what works better for
which kind of pointer usage.
Copyright (C) 2010 Zed. A. Shaw
Credits