- Learn C The Hard Way
- Preface
- Introduction: The Cartesian Dream Of C
- Exercise 0: The Setup
- Exercise 1: Dust Off That Compiler
- Exercise 2: Make Is Your Python Now
- Exercise 3: Formatted Printing
- Exercise 4: Introducing Valgrind
- Exercise 5: The Structure Of A C Program
- Exercise 6: Types Of Variables
- Exercise 7: More Variables, Some Math
- Exercise 8: Sizes And Arrays
- Exercise 9: Arrays And Strings
- Exercise 10: Arrays Of Strings, Looping
- Exercise 11: While-Loop And Boolean Expressions
- Exercise 12: If, Else-If, Else
- Exercise 13: Switch Statement
- Exercise 14: Writing And Using Functions
- Exercise 15: Pointers Dreaded Pointers
- Exercise 16: Structs And Pointers To Them
- Exercise 17: Heap And Stack Memory Allocation
- Exercise 18: Pointers To Functions
- Exercise 19: A Simple Object System
- Exercise 20: Zed's Awesome Debug Macros
- Exercise 21: Advanced Data Types And Flow Control
- Exercise 22: The Stack, Scope, And Globals
- Exercise 23: Meet Duff's Device
- Exercise 24: Input, Output, Files
- Exercise 25: Variable Argument Functions
- Exercise 26: Write A First Real Program
- Exercise 27: Creative And Defensive Programming
- Exercise 28: Intermediate Makefiles
- Exercise 29: Libraries And Linking
- Exercise 30: Automated Testing
- Exercise 31: Debugging Code
- Exercise 32: Double Linked Lists
- Exercise 33: Linked List Algorithms
- Exercise 34: Dynamic Array
- Exercise 35: Sorting And Searching
- Exercise 36: Safer Strings
- Exercise 37: Hashmaps
- Exercise 38: Hashmap Algorithms
- Exercise 39: String Algorithms
- Exercise 40: Binary Search Trees
- Exercise 41: Using Cachegrind And Callgrind For Performance Tuning
- Exercise 42: Stacks and Queues
- Exercise 43: A Simple Statistics Engine
- Exercise 44: Ring Buffer
- Exercise 45: A Simple TCP/IP Client
- Exercise 46: Ternary Search Tree
- Exercise 47: A Fast URL Router
- Exercise 48: A Tiny Virtual Machine Part 1
- Exercise 48: A Tiny Virtual Machine Part 2
- Exercise 50: A Tiny Virtual Machine Part 3
- Exercise 51: A Tiny Virtual Machine Part 4
- Exercise 52: A Tiny Virtual Machine Part 5
- Next Steps
- Deconstructing K & RC Is Dead
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 <stdio.h>
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'si
away from there. - Converting that address at
ages+i
into a validint
of the right size, such that the index works to return what you want: the int at indexi
.
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 atnames
. 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 forprintf
. - 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
towhile-loops
and see what works better for which kind of pointer usage.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论