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 variableipis a pointer to an object of typeint; generally, if the object type isT, then the pointer’s type isT*. The specialvoid *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
sizeof the pointer’s type. For example, withchar*, reading one byte from the start address ends the read; withint*, 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.