Algorithms
and
Data
Structures

Spring 2022
course
site
-->

C pointers and all that


Consider this python code. What's going on? Run in pythontutor.com.

a = [1, 2, 3]
b = [1, 2, 3]
print("a is b ? ", a is b)
print("id(a) ", hex(id(a)))
print("id(b) ", hex(id(b)))

c = b
print("id(c) ", hex(id(c)))
print("c is b ? ", c is b)

Python doesn't use these "identities" of variables, except to tell whether or not two variables refer to the same address in memory.

But C uses address quite a lot. Here are two new operators that you may be unfamiliar with : * and & .

// --- ptrs .c ---
#include <stdio.h>
int main(){
  int    a = 1234;
  int    b = 5678;
  int*   a_p = &a;        // pointer to a
  int*   b_p = &b;        // pointer to b
  int**  a_pp = &a_p;     // pointer to pointer to a
  int**  b_pp = &b_p;     // pointer to pointer to b

  printf("--- address and the values within them --- \n");
  printf("&a,     a     : %12p  %14d \n", &a,    a);
  printf("&b,     b     : %12p  %14d \n", &b,    b);
  printf("&a_p,   a_p   : %12p  %12p \n", &a_p,  a_p);
  printf("&b_p,   b_p   : %12p  %12p \n", &b_p,  b_p);
  printf("&a_pp,  a_pp  : %12p  %12p \n", &a_pp, a_pp);
  printf("&b_pp,  b_pp  : %12p  %12p \n", &b_pp, b_pp);
  printf("--- follow the pointer --- \n");
  printf("*a_p   : %d \n", *a_p);
  printf("**a_pp : %d \n", **a_pp);
  printf("--- sizes of things (in bytes) --- \n");
  printf("sizeof(int)   : %lu \n", sizeof(int));
  printf("sizeof(int*)  : %lu \n", sizeof(int*));
  printf("sizeof(int**) : %lu \n", sizeof(int**));

  return 0;
}

Compiling and running that on jupyter.bennington gives

jim@jupyter:~$ gcc ptrs.c -o ptrs
jim@jupyter:~$ ./ptrs
--- address and the values within them ---
&a,     a     : 0x7fff10f261e0            1234
&b,     b     : 0x7fff10f261e4            5678
&a_p,   a_p   : 0x7fff10f261e8  0x7fff10f261e0
&b_p,   b_p   : 0x7fff10f261f0  0x7fff10f261e4
&a_pp,  a_pp  : 0x7fff10f261f8  0x7fff10f261e8
&b_pp,  b_pp  : 0x7fff10f26200  0x7fff10f261f0
--- follow the pointer ---
*a_p   : 1234
**a_pp : 1234
--- sizes of things (in bytes) ---
sizeof(int)   : 4
sizeof(int*)  : 8
sizeof(int**) : 8

Discuss what this is all about.

The notation is set up so that int* i_ptr; and int *i_ptr are both OK, and mean the same thing: i_ptr is a pointer to (i.e. the address of) an integer.

In C, an array like int numbers[10] actually means that numbers is essentially an int*, a pointer to an int, which is the first element of the array. So numbers[0] is also *numbers, and numbers[1] is also *(numbers+1).

memory allocation

Consider the following python code.

def foo():
    a = [1,2,3]
    print("id(a) is ", id(a))
    b = [4,5,6]
    return a

aa = foo()
print(aa)
print("id(aa) is ", id(aa))
print(b)

Quick quiz: what happened to the memory where [1,2,3] was? what happened to the memory where [4,5,6] was? How does the program know the difference? Was the whole array returned ... or just a reference to it? (Hint: is id(a) the same or different than id(aa)?)

In C, just like in Python, we don't usually copy an array when we return it - instead we return its address. But we also have to make sure that it exists even when the function stops running. (Python does that for you.)

You might try to do this ...

// THIS CODE HAS A SERIOUS BUG IN IT.
int* foo(){
  int a[] = {1,2,3};
  int b[] = {4,5,6};
  return a;     // Here "a" is the same as &a[0], address of array.
}

... but you would be cruising for a bruising. The address of where those numbers were is returned, but the memory itself is not kept. In C, each function's local variables are explicitly declared, and all are up for grabs once the function is finished.

If in C you want to have a function that creates and returns a block of memory, you must instead use malloc (i.e. "memory allocate") or a similar system function, which reserves some memory off in a special place for dynamic memory for that process. The call to malloc returns a pointer to that memory.

// THIS IS OK.
int* foo(){
  int* a = malloc(3 * sizeof(int)); // space for 3 integers
  int i;
  for (i=1; i<=3; i++){ a[i] = i; }  // fill with 1,2,3
  return a;     
}

C is a primitive, low level language. You can't even tell how long the array is without sending along its size as another parameter!


Here is the approach that I like in C. We define a new type which is a pointer to to a block of memory (a struct). We also set up functions which allocate one of those blocks. And then we only use those pointers.

A version of this with more commentary is attached.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

typedef struct _mything *mything;
struct _mything {
    int count;           // an integer value
    char name[32];       // a string of 32 characters.
};

mything new_mything(){
  // allocate a mything block of memory; return a pointer to it.
    mything newthing = malloc(sizeof(struct _mything));
    newthing->count = 0;             // initialize its count
    strcpy(newthing->name, "");      // copy empty string to it's name.
    return newthing;
}

int main(){
    mything george = new_mything();
    george->count = 33;
    strcpy(george->name, "George");

    printf("The object has count=%i and name=%s.\n",
           george->count, george->name);

    return 0;
}

And here are more examples of all this sort of stuff :

https://cs.bennington.college /courses /spring2022 /algorithms /notes /pointers
last modified Wed March 2 2022 11:53 pm

attachments [paper clip]

  last modified size
TXT c_object_template.c Wed Mar 02 2022 11:53 pm 2.6K