8

Thanks to feedback in this thread, I'm beginning to plan how to integrate a basic introduction to pointers into my unit on arrays. Here's a sample program I just wrote up:

#include <stdio.h>

int main(void)
{
    int number = 42;
    printf("Value of number: %d\n", number);
    printf("Address of number: %p\n", &number);

    int *pNumber = &number;
    printf("Value of number pointer: %p\n", pNumber); 
    printf("Value at address of number pointer: %d\n", *pNumber);    
}

I will probably add comments before actually presenting it and making it available to students. Seeing how the address of number and the value of pNumber always correspond will hopefully reinforce how pointers work. The first and last line will match as well. The idea is to show as clear and simple an example as possible.

My question comes in two parts:

  1. Is this an appropriate example for introducing pointers?

  2. Do you have suggestions for similar simple examples of basic pointer usage?

Ellen Spertus
  • 7,630
  • 4
  • 31
  • 54
Peter
  • 9,082
  • 2
  • 22
  • 61

5 Answers5

8

I would teach the use of arrays and array indices first (with pictures of a long row of physical mailboxes, or school lockers, etc.) Then later explain that all of a computer's and/or C program's (process) memory can be thought of as one big array with a pointer being a form of index (locker number) into that (virtual) memory array.

You can then draw a picture of how arrays of char (bytes) might be allocated in memory. And then show why pointers might be necessary in order to tell a standard string function how to find (or modify) a substring in an arbitrary C string array.

hotpaw2
  • 1,895
  • 8
  • 14
  • 1
    I would teach about the computer's memory as a big array first, then teach about how pointers and arrays interact with it. Because the memory is what *is*, and the notation is what works with it. Why does everyone want to go from Formal to Concrete, in complete defiance of Piaget? –  Jul 03 '17 at 17:06
  • An interesting question is whether memory is an example of a concrete array, or an array is an example of a concrete memory system (of capacitors, electron wells, magnets, sound waves, beads, knots, etc.) – hotpaw2 Jul 04 '17 at 15:27
  • 2
    How computers are constructed might change over time, but the ideas of variables and memory and pointers are unlikely to change. The students need to learn an abstraction in order to program, I am simply arguing for teaching *the simplest and most straightforward and general abstraction possible*. I have been thinking about a question along these lines, because it comes up a lot when teaching, but I have not finished formulating the question yet. –  Jul 04 '17 at 15:41
6

What you have there is an excellent start, and is completely appropriate. I would go next into a puzzle to try to get them hooked. I would type the following code in front of them on your projector, and ask them to pair with a neighbor to try to figure out what will happen next.

(Note, for this to work properly, you must turn off stack security in gcc, which according to this somewhat dated post might involve the flags -fno-stack-protector and -fno-stack-protector-all.)

#include <stdio.h>

int main() {
    int a = 15;
    int b = 30;
    int c = 45;

    int *whatsthis = &a;
    printf("whatsthis is: %d\n",*whatsthis);

    whatsthis = whatsthis + 4;
    printf("whatsthis is: %d\n",*whatsthis);

    return 0;
}

(Depending on your compiler and environment, you may have to muck around with the code a bit to get 45 as your output for the second printf)

If your kids are like mine, they probably won't be able to figure this out. But once you compile it and execute, ask them to go back to their neighbor and try to figure out what in the world happened.

At this point, several of the groups should be able to figure out that it has something to do with the ints being stored next to each other, and, if pressed just a bit, that the number 4 was derived using the byte size of an int.

This gives you a perfect segue into arrays. Since you're taking all of this trouble to go into some depth (using references to get at arrays), I would try for a few takeaways from the lesson:

  1. Pointer arithmetic means changing the address you're looking at.
  2. The actual formula used to find the location of spot n in an array (which shows why that first address needs to be 0: (startlocation + (n * itemsize))
  3. Therefore, indexing in arrays can be characterized as a distance from the start.
  4. Array items are packed together with no wasted space and no separators between the data.
  5. That, in fact, these are the only two tools that computers possess to locate themselves in memory: having items packed next to each other so that they can go from one item to the next, to the next, or saving pointers in memory so that we can go back to places later. Every other thing that computers do with memory can be reduced to one of these two actions.
Ben I.
  • 32,726
  • 11
  • 68
  • 151
  • 2
    @Peter The code example is wrong, because the unit for pointer arithmetic is the size of the pointed-to type. whatsthis + 4 points 4 ints ahead of whatsthis (Or, rather, it would if whatsthis were pointing into an array large enough to do that. The code as written invokes undefined behavior.) – Eric M Schmidt Jun 18 '17 at 06:31
  • @EricMSchmidt I believe Ben I. was aware of this when posting the example. Even the undefined behavior can be teachable for students to learn pointers (and the precautions that one must take with them). At least I think this was intentional... – Peter Jun 18 '17 at 14:38
  • 1
    Yes, the undefined behavior was the purpose of the exercise. By arranging the demonstration so that the wrong variable gets modified, it should both pique their interest and give a great entrée to the idea of separating a memory address from the data it stores. – Ben I. Jun 18 '17 at 15:00
  • I don't get why you're adding 4 to go from *a* to *c* when they should be *two* addresses apart, and yet you say it's *four* bytes per int. Where is *b*, then? (Nevertheless, +1 for a great illustration.) – Wildcard Jul 25 '17 at 20:12
  • @Wildcard I tried to clarify the text. IIRC, on my compiler (I tested my code when I wrote this), ints were 2 byes. – Ben I. Jul 25 '17 at 20:44
  • Got it, thanks. It wasn't the answer text that caused the confusion; it was your earlier comment: *"Mine used a 4-byte integer, which is also permitted."* I understand now that was just a slip. Thanks for clarifying! :) – Wildcard Jul 25 '17 at 20:57
  • Uh, how can we know what the distance is between the two variables, at all? Couldn't they be anywhere in the address space, in any order? – SilverWolf Dec 22 '17 at 18:39
  • @seaturtle gcc can disable stack protections: https://stackoverflow.com/q/2340259/4151129 – Ben I. Dec 22 '17 at 20:08
  • Huh. I didn't know that was on _purpose_, I always figured that was just how it was, like file fragmentation on disk. Thanks. (: – SilverWolf Dec 23 '17 at 00:45
  • I must say that I'm still very confused by this example. It's never explicitly stated here: What do you *expect* this code to print? I'll point out that on my current GCC compiler, if I add one or more printf statements *after* these lines, then the positions of a, b, c in memory get re-ordered -- and so I can get this same code to print out any of 30, 45, or 0 (i.e., anything). If your point is truly that this is undefined and might do anything, then what do you actually mean by "be able to figure this out" for the students in this context? – Daniel R. Collins Mar 24 '21 at 14:58
  • 1
    @DanielR.Collins With modern, stack-randomizing compilers, you are correct, and I will add a caveat to that effect. Because of my schedule, I typically teach this as part of a computer security class on stack-based attacks, and we purposefully use dated virtual machines without stack randomization for exactly the reason you cite. I wasn't thinking about that when I wrote the answer, though. Oops! – Ben I. Mar 24 '21 at 17:14
  • @BenI.: Is that really a stack-randomizing issue, though? With my current stock copy of GCC, the ordering is fixed/deterministic with any particular piece of code. However, there are some complicated rules which are opaque to predict. For example, if I make 4 int variables and print any or all values, then GCC puts them in declaration order. But if I print out the address of one of them, then GCC puts that one in the top slot on the stack. Etc. – Daniel R. Collins Mar 28 '21 at 15:20
  • Perhaps you should put more detail/a link with the "you must turn off stack randomization in gcc" piece, because my other searches for that aren't getting hits. – Daniel R. Collins Mar 28 '21 at 15:21
  • @DanielR.Collins I edited the post again! I'm not aware of all of the protections used for stacks, tbh. Like I said, I do this on old virtual machines that have no stack protections built into GCC at all for exactly these pedagogic demonstration purposes, so there may be fine distinctions that I am missing here in more modern setups. – Ben I. Mar 28 '21 at 16:44
  • @BenI.: Probably good that you removed the word "randomization" there. Unfortunately I'm still skeptical that the current detail is correct. (a) "-fno-stack-protector-all" isn't a valid GCC flag (see comments to what you linked for that). (b) I don't think there's any sign that these buffer protections involve re-ordering the stack (I just tested and see no difference)... – Daniel R. Collins Mar 28 '21 at 20:00
  • Note that any time a question gets asked about GCC stack ordering in particular, the answers usually are some form of "there is absolutely no way to tell or control it without looking into the GCC code about how it makes its choices". E.g.: https://stackoverflow.com/questions/66793187/why-does-gcc-order-integers-on-the-stack-in-this-way – Daniel R. Collins Mar 28 '21 at 20:06
4

I would use more visual examples that target conceptual knowledge first. I would do a TON of memory diagrams with code tracing. Then, introduce the idea of pointers as memory addresses like you do in your example.

I am a big fan of Nick Parlante's pointer materials. I know they are vintage at this point, but I haven't found anything better at explaining pointers conceptually or at targeting common pointer errors like dereferencing pointers that don't point to anything (seg fault!).

Binky Pointer Fun video

This claymation video (Original webpage, YouTube version in C) is hilarious and a crowd-pleaser, but also explains pointers conceptually---without talking about memory addresses explicitly. For that reason, it has versions in C, C++, Java, and more.

Three Pointer Rules

This is my version of Nick Parlante's Three Pointer Rules. Definitely see the Pointer Basics document for full details and the original rules.

1) Drawing Pointers and Pointees

Draw a pointer as a box. [C-specific modification] Before it points to anything, that box has a "?" in it.

int *x;

enter image description here

When you allocate memory (make a pointee), draw it in the heap. Setting that pointee equal to x makes an arrow go from the pointer to the pointee.

x = malloc(sizeof(int));

enter image description here

Dereferencing changes the pointee---the thing the pointer points to (see next rule)

*x = 42;

enter image description here

2) Dereferencing

The dereference operation starts at the pointer and follows its arrow over to access its pointee. Important: The dereference operation on a pointer only works if the pointer has a pointee -- the pointee must be allocated and the pointer must be set to point to it. You will save students so many seg faults if you emphasize this!

3) Pointer Assignment

Pointer assignment between two pointers makes them point to the same pointee. So the assignment y = x; makes y point to the same pointee as x.

Connecting Pointer rules to code

To drive it home, walk through code line-by-line while drawing diagrams. Have students work on their own code tracing and diagrams in groups.

Note that Nick Parlante emphasizes dynamically allocated memory in his code examples (malloc), and avoids the "address of" operator & . I think this is a smart choice because it's what is most common in real code. But of course & can be introduced in this method.

Here are some simple sample code snippets to start the walk-throughs and memory drawings:

// non-pointer code
double num;
num = 42.7;
printf(num);

// pointer code
double* num_ptr;;
num_ptr = malloc(sizeof(double));
*num_ptr = 42.7;
printf(*num_ptr);

// Binky's code
int* x;
int* y;
x = malloc(sizeof(int));
*x = 42;
*y = 13; // yes it crashes, but that's the point >:)
y = x;
*y = 13;
nova
  • 1,965
  • 8
  • 17
4

Obviously, beginners in C will have to deal with addresses and pointers really soon.

First, you owe them an explanation about addresses for

int aNumber;
scanf ("%d", & aNumber);   // an address  
printf("%s",   aNumber);   // a value

which will appear in your first examples. So the "address of" operator is known from the start.

Second, pointers appear as soon as you define functions with parameters "by reference".

Defining functions is a fundamental activity in programming. It is about giving names to pieces of code, thus introducing "user defined abstractions" to reduce the complexity of problems. And choosing them well so they can be reused. So the introduction of functions should come very quickly.

Third: historically arrays (in prehistoric C) were introduced after pointers. An array is (was) simply a pointer automatically initialized with the address of the first byte of an empty space automatically allocated on the call stack. Later in C it became a constant pointer (except in function parameters....). This poor conception explains why arrays in C have a strange status (you can't assign them like structures, for example - but when you pass them as arguments, you don't need the stinking &) which is a difficulty for beginners. Maybe the problem is with the arrays, not with pointers.

Four: As long as you use pointers for arguments transmission, you don't have any necessity to talk about dynamic allocation, which is an entirely different topic. Except you use a pointer to store the result of malloc().

And last: another point is the data structures built from pointers (linked lists, trees,...) and the basic algorithms.

EDIT: as an answer to your question: in general I try to avoid artificial examples, because students don't see the link with their usual programming tasks. Function that modify their arguments are more natural: swapping/ordering things, modifying a struct, etc.

Glorfindel
  • 169
  • 1
  • 1
  • 10
Michel Billaud
  • 937
  • 4
  • 10
4

Yours is a fine first lesson, although it does not show the value of pointers. I would suggest also demonstrating how pointers can be used to implement a swap function.

Ask the students (or step them through) what this code does:

void swap(int x, int y) {
    int temp = x;
    x = y;
    y = temp;
}

int main() {
    int a = 1;
    int b = 2;
    printf("Before calling swap(): a = %d, b = %d\n", a, b);
    swap(a, b);
    printf("After calling swap(): a = %d, b = %d\n", a, b);
}

As you know, the local variables a and b do not get changed, because C uses call by value.

At that point, I show and discuss this cartoon:

Cartoon in which doctor tells patient: "Your x-ray shows a broken rib, but we fixed it with photoshop."

(This cartoon might be too risqué.)

I then teach and step through the correct way to write and call a swap procedure:

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int a = 1;
    int b = 2;
    printf("Before calling swap(): a = %d, b = %d\n", a, b);
    swap(&a, &b);
    printf("After calling swap(): a = %d, b = %d\n", a, b);
}

This provides a simple demonstration of the benefits of pointers and will help them understand why primitives (not counting pointers) are passed by value, while everything else appears to be passed by reference.

Ellen Spertus
  • 7,630
  • 4
  • 31
  • 54