While reading Computer Systems: A Programmer’s Perspective, I came across the section on understanding pointers, which is excellent, and I’ve summarized my study of it here. Some of the content below is excerpted from the book, and some is my own study notes.

Understanding Pointers

Pointers are a core feature of the C language, providing a uniform way to reference elements within different data structures. The following highlights some key principles of pointers and how they map to machine code.

  • Every pointer has a type. This type indicates what kind of object the pointer points to. Take the declaration int *ip; as an example: the variable ip is a pointer to an object of type int; generally, if the object type is T, then the pointer’s type is T*. The special void * type represents a generic pointer. For instance, void *malloc(size_t size) returns a generic pointer, which is then converted into a typed pointer via an explicit cast or an implicit cast such as an assignment. The pointer type is not part of the machine code; it is an abstraction provided by C to help programmers avoid addressing errors.

One way to understand this: the concrete address value of a pointer is the starting address for addressing, and when the read ends is determined by the size of the pointer’s type. For example, with char*, reading one byte from the start address ends the read; with int*, reading four bytes from the start address ends it. Let’s write a piece of C code to verify our understanding of pointers.

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

typedef enum NodeType {
    INT,
    CHAR
} NodeType;

typedef struct Node {
    NodeType type;
} Node;

typedef struct IntNode {
    NodeType type;
    int value;
} IntNode;

typedef struct CharNode {
    NodeType type;
    char value;
} CharNode;

char* printNodeType(NodeType type) {

    switch (type)
    {
    case INT:
        return "INT";    
    case CHAR:
        return "CHAR";
    default:
        return "UNKNOW";
    }

    return NULL;
}

// Focus on understanding this function
void printNode(Node* n) {
    assert(n != NULL);

    switch (n->type)
    {
    case INT:
        {
            IntNode* in = (IntNode*)n;
            printf("type:%s value:%d \n", printNodeType(in->type), in->value);
        }
        break;
    case CHAR:
        {
            CharNode* cn = (CharNode*)n;
            printf("type:%s value:%d \n", printNodeType(cn->type), cn->value);
        }
        break;
    default:
        printf("unknow node \n");
        break;
    }
}

int main() {
    IntNode *a = (IntNode*)malloc(sizeof(IntNode));
    a->type = INT;
    a->value = 1024;
    printNode(a);

    CharNode *b = (CharNode*)malloc(sizeof(CharNode));
    b->type = CHAR;
    b->value = 'a';
    printNode(b);

    return 0;
}

Program output:

type:INT value:1024 
type:CHAR value:97 

This code verifies the understanding above.

  • Every pointer has a value. This value is the address of an object of the specified type. The special NULL(0) value means the pointer points nowhere.
  • Casting a pointer from one type to another only changes its type, not its value. One effect of the cast is to change the scaling of pointer arithmetic.

The code example above verifies this point.

  • A pointer can also point to a function. The value of a function pointer is the address of the first instruction in the machine-code representation of that function.