Skip to main content

Lecture 7: Pointers

Prerequisites: Lecture 4 Arrays (array declaration, indexing, iteration); Lecture 5 Functions (function definition, parameter passing, return values)

1. Overview

  • Every variable in C resides at a specific memory address; a pointer is a variable that stores such an address, thereby enabling programmes to manipulate data indirectly rather than only by name.
  • After completing this chapter you will be able to declare and dereference pointers, perform pointer arithmetic over arrays, pass addresses into functions so that they may modify the caller's data (call-by-reference), allocate memory dynamically, and use function pointers to write flexible, callback-driven code.

2. Learning Outcomes

Upon successful completion of this chapter, students will be able to:

  1. Explain what a pointer is, declare pointer variables of various types, and distinguish the declaration operator * from the dereference operator *.
  2. Use the address-of operator & and the dereference operator * to read and modify variables indirectly.
  3. Initialise pointers to NULL and perform defensive checks before dereferencing.
  4. Traverse and manipulate arrays using pointer notation and pointer arithmetic (ptr++, *(ptr + i)).
  5. Pass pointers to functions to achieve call-by-reference semantics, enabling in-place modification of the caller's variables and arrays.
  6. Allocate and free heap memory with malloc() and free(), checking for allocation failure.
  7. Declare and use pointer-to-pointer variables to achieve an additional level of indirection.
  8. Declare, assign, and invoke function pointers, and apply them to build simple dispatch mechanisms such as a calculator.
  9. Combine pointers with bitwise operators to manipulate individual bits of data.

3. Key Terms

Term (EN)中文Notes
pointer指针A variable whose value is a memory address.
address-of operator &取地址运算符Yields the address of its operand; do not confuse with the bitwise AND &.
dereference operator *解引用运算符Accesses the value stored at the address held by a pointer; same symbol as the declaration *, but used in a different syntactic position.
NULL pointer空指针A pointer that points to nothing; defined in <stdio.h> and <stdlib.h>. Dereferencing NULL causes undefined behaviour.
pointer arithmetic指针算术Adding or subtracting an integer to/from a pointer; the offset is automatically scaled by sizeof the pointed-to type.
call by reference引用传递Passing the address of a variable to a function so that the function can modify the original. C simulates this via pointers.
malloc() / free()动态内存分配 / 释放Library functions in <stdlib.h> for heap allocation. Always check the return value of malloc() and always call free() when the memory is no longer needed.
pointer to pointer二级指针A pointer that stores the address of another pointer, e.g. int **pp;.
function pointer函数指针A pointer that stores the address of a function; enables indirect (dynamic) function calls. Syntax: return_type (*name)(param_list);.
dangling pointer悬空指针A pointer that refers to memory that has been freed or has gone out of scope; dereferencing it is undefined behaviour.

4. Core Concepts

4.1 What Is a Pointer?

In C, every object (variable, array element, structure, etc.) occupies one or more bytes of memory, and each byte has a unique numeric address. A pointer is simply a variable that holds such an address. Because the pointer "points to" the location of another object, it provides an indirect means of accessing or modifying that object's value.

Pointers are fundamental to C for three principal reasons: they enable dynamic memory allocation (requesting memory at run-time), they allow efficient array and string manipulation (traversing data without copying), and they make it possible to pass data between functions by reference (so that a called function can alter the caller's variables).

Minimal example
File: p07_a.c
Build: gcc p07_a.c -o p07_a
Run: ./p07_a

#include <stdio.h>

int main(void) {
int n = 42;
int *ptr = &n; /* ptr holds the address of n */

printf("Value of n : %d\n", n);
printf("Address of n : %p\n", (void *)&n);
printf("Value stored in ptr: %p\n", (void *)ptr);
printf("Value via *ptr : %d\n", *ptr);

return 0;
}

What you should see: The direct value of n (42), the hexadecimal address of n, the same address stored in ptr, and the value 42 obtained by dereferencing ptr.

Takeaway: A pointer stores an address; the dereference operator * retrieves the value at that address.

4.2 Pointer Declaration and the Dereference Operator

A pointer variable is declared by placing the * symbol between the data type and the variable name. The data type specifies what kind of object the pointer will point to. The * token serves double duty: in a declaration it indicates "this variable is a pointer", and in an expression it acts as the dereference (indirection) operator, meaning "give me the value at this address". All three of the following forms are syntactically valid and semantically identical:

int *p; /* asterisk adjacent to name – most common style */
int* p; /* asterisk adjacent to type */
int * p; /* asterisk with spaces on both sides */

When declaring multiple variables on a single line, take care: int *s, t; declares s as a pointer to int and t as a plain int. To declare two pointers, write int *s, *t;.

Minimal example
File: p07_b.c
Build: gcc p07_b.c -o p07_b
Run: ./p07_b

#include <stdio.h>

int main(void) {
int val = 10;
int *ptr;

ptr = &val; /* & obtains the address of val */
printf("val = %d\n", *ptr); /* * dereferences the pointer */

*ptr = 20; /* modify val indirectly */
printf("val = %d\n", val);

return 0;
}

What you should see: val = 10 followed by val = 20.

Takeaway: The address-of operator & yields an address; the dereference operator * follows that address back to the stored value—and can also be used on the left-hand side of an assignment to modify that value.

4.3 Pointer Assignment — Step by Step

To build a reliable mental model it is helpful to trace how memory changes as pointer operations execute. Consider the following sequence:

int num = 42; /* (a) num occupies, say, address 0x1000 and holds 42 */
int *p_num; /* (b) p_num is declared but uninitialised */

p_num = &num; /* (c) p_num now stores 0x1000 */
printf("%d\n", *p_num); /* prints 42 */

*p_num = 99; /* (d) the int at 0x1000 is overwritten with 99 */
printf("%d\n", num); /* prints 99 */

At stage (a), num holds 42 and p_num contains an indeterminate value. After the assignment p_num = &num in stage (c), p_num holds the address of num. The dereference *p_num therefore evaluates to 42. Finally, writing *p_num = 99 modifies the memory at address 0x1000, so num itself becomes 99. The variable name num and the expression *p_num are, after the assignment, two different ways of referring to the same storage location.

Takeaway: Assigning an address to a pointer creates an alias; any change made through *ptr is immediately visible through the original variable name, and vice versa.

4.4 The NULL Pointer

When a pointer is declared without initialisation its value is indeterminate—it may point to any arbitrary location in memory. Dereferencing such a pointer invokes undefined behaviour, which can crash the programme or silently corrupt data. To guard against this, C provides the constant NULL (defined in <stdio.h> and <stdlib.h>), which represents a pointer that intentionally points to nothing.

Good practice dictates:

  1. Initialise every pointer to NULL unless you are assigning a valid address immediately.
  2. Before dereferencing, check whether the pointer is NULL.
  3. After calling free(), reset the pointer to NULL to prevent dangling-pointer errors.

Minimal example
File: p07_c.c
Build: gcc p07_c.c -o p07_c
Run: ./p07_c

#include <stdio.h>

int main(void) {
int num = 42;
int *ptr = NULL; /* safe initialisation */

if (ptr == NULL) {
printf("Pointer is NULL. Assigning address...\n");
ptr = &num;
}

printf("Value pointed to by ptr: %d\n", *ptr);

ptr = NULL; /* reset after use */
return 0;
}

What you should see: A message indicating that the pointer was NULL, followed by Value pointed to by ptr: 42.

Takeaway: Always initialise pointers to NULL and check before dereferencing; this single habit prevents a large class of run-time errors.

4.5 Pointers and Arrays

In C, the name of an array, when used in most expression contexts, decays to a pointer to its first element. That is, given int arr[5];, the expression arr is equivalent to &arr[0]. Consequently, a pointer initialised with ptr = arr; can be used to traverse the entire array.

There are two principal notations for accessing the i-th element through a pointer:

NotationMeaning
*(ptr + i)Add i units (each of size sizeof(int)) to the base address, then dereference.
ptr[i]Syntactic sugar for *(ptr + i); entirely equivalent.

Both *(ptr + i) and ptr[i] yield the same machine code in virtually all compilers.

Minimal example
File: p07_d.c
Build: gcc p07_d.c -o p07_d
Run: ./p07_d

#include <stdio.h>

int main(void) {
int arr[] = {10, 20, 30};
int *ptr = arr; /* equivalent to &arr[0] */

for (int i = 0; i < 3; i++) {
printf("%d ", *(ptr + i));
}
printf("\n");

return 0;
}

What you should see: 10 20 30

Takeaway: An array name decays to a pointer to its first element; pointer notation *(ptr + i) and subscript notation ptr[i] are interchangeable.

4.6 Pointer Arithmetic

C permits adding or subtracting integer values to/from a pointer. The compiler automatically scales the offset by the size of the pointed-to type. For example, if ptr is an int * and sizeof(int) is 4 bytes, then ptr + 1 advances the address by 4 bytes—exactly one int element. Similarly, ptr++ advances the pointer to the next element, and ptr-- moves it to the previous one.

This mechanism is what makes pointer-based array traversal both natural and efficient. However, the programmer must ensure that the pointer remains within the bounds of the allocated object (or exactly one past the end); out-of-bounds pointer arithmetic is undefined behaviour.

Minimal example
File: p07_e.c
Build: gcc p07_e.c -o p07_e
Run: ./p07_e

#include <stdio.h>

int main(void) {
int arr[] = {10, 20, 30};
int *ptr = arr;

for (int i = 0; i < 3; i++) {
printf("%d ", *ptr);
ptr++; /* advance to next element */
}
printf("\n");

return 0;
}

What you should see: 10 20 30

Takeaway: Pointer arithmetic is scaled by sizeof(*ptr); incrementing an int * advances the address by sizeof(int) bytes, which is precisely one array element.

4.7 Using Pointers to Read Input into an Array

Because scanf() requires the address of the destination variable, pointer notation and scanf() work together seamlessly. Given int *p = arr;, the expression p + i already yields the address of arr[i], so one may write scanf("%d", p + i) without the & operator.

Minimal example
File: p07_f.c
Build: gcc p07_f.c -o p07_f
Run: ./p07_f

#include <stdio.h>

int main(void) {
int arr[5];
int *p = arr;

printf("Enter 5 integers:\n");
for (int i = 0; i < 5; i++) {
scanf("%d", p + i); /* p + i == &arr[i] */
}

printf("You entered:\n");
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i));
}
printf("\n");

return 0;
}

What you should see: The five integers entered by the user, echoed back on a single line.

Takeaway: Since p + i is already an address, scanf needs no additional & when receiving input through pointer notation.

4.8 Pointers as Function Parameters (Call by Reference)

C passes all function arguments by value: the function receives a copy of the argument, so modifications within the function do not affect the original. To allow a function to modify the caller's variable, one passes the address of the variable (using &) and declares the parameter as a pointer. Inside the function, dereferencing the pointer accesses—and can modify—the original data. This idiom is commonly called call by reference (or, more precisely, simulated call by reference).

The general form is:

return_type function_name(data_type *param) {
/* use *param to read or write the original variable */
}

Minimal example — swap two integers
File: p07_g.c
Build: gcc p07_g.c -o p07_g
Run: ./p07_g

#include <stdio.h>

void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}

int main(void) {
int x = 5, y = 10;

printf("Before swap: x = %d, y = %d\n", x, y);
swap(&x, &y);
printf("After swap: x = %d, y = %d\n", x, y);

return 0;
}

What you should see: Before swap: x = 5, y = 10 followed by After swap: x = 10, y = 5.

Takeaway: Passing a pointer to a function gives the function the ability to modify the caller's data; this is the standard C mechanism for returning multiple values or altering arguments in place.

4.9 Dynamic Memory Allocation

All variables declared inside a function reside on the stack and are automatically destroyed when the function returns. When the required amount of memory is not known at compile time, or when data must outlive the function that creates it, the programmer must allocate memory on the heap using malloc() (declared in <stdlib.h>).

malloc(size) returns a void * pointing to a block of size bytes, or NULL if the system cannot satisfy the request. The returned pointer must be cast (or implicitly converted) to the appropriate type. Once the memory is no longer needed, free() must be called to release it; failure to do so causes a memory leak.

A robust allocation pattern is:

int *arr = malloc(n * sizeof(int));
if (arr == NULL) {
fprintf(stderr, "Memory allocation failed!\n");
return 1; /* or exit(1) */
}
/* ... use arr ... */
free(arr);
arr = NULL; /* prevent dangling pointer */

Minimal example
File: p07_h.c
Build: gcc p07_h.c -o p07_h
Run: ./p07_h

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

int main(void) {
int *arr = malloc(5 * sizeof(int));

if (arr == NULL) {
printf("Memory allocation failed!\n");
return 1;
}

for (int i = 0; i < 5; i++) {
arr[i] = i * i; /* 0, 1, 4, 9, 16 */
}

for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");

free(arr);
arr = NULL;

return 0;
}

What you should see: 0 1 4 9 16

Takeaway: Use malloc() for run-time-sized allocations, always check for NULL, and always free() when finished.

4.10 Pointer to Pointer

Just as a pointer stores the address of an ordinary variable, a pointer to a pointer (double pointer) stores the address of another pointer. It is declared with two asterisks: int **pp;. To access the ultimate value, one applies the dereference operator twice: **pp.

Double pointers are used in practice when a function must modify a pointer passed to it (e.g., to re-allocate a buffer), and they also underlie the representation of two-dimensional dynamic arrays.

Minimal example
File: p07_i.c
Build: gcc p07_i.c -o p07_i
Run: ./p07_i

#include <stdio.h>

int main(void) {
int val = 42;
int *p = &val; /* p holds the address of val */
int **pp = &p; /* pp holds the address of p */

printf("val via **pp: %d\n", **pp);

return 0;
}

What you should see: val via **pp: 42

Takeaway: A pointer to a pointer adds one level of indirection; **pp follows two addresses to reach the underlying value.

4.11 Objects versus Functions in C

The C standard classifies every entity as either an object or a function. An object is a named region of storage that holds data (e.g., int, char[], struct, arrays, pointers). A function is a named block of code that performs a computation. Crucially, both objects and functions have addresses, and therefore both can be referenced by pointers. However, the two categories of pointers are distinct: an object pointer (e.g., int *) and a function pointer (e.g., int (*)(int, int)) are not interchangeable.

Takeaway: Understanding that functions, like variables, reside at memory addresses is the key insight that makes function pointers possible.

4.12 Function Pointers

A function pointer is a variable that stores the address of a function. It may then be used to call that function indirectly. The declaration syntax is:

return_type (*pointer_name)(parameter_list);

Note the parentheses around *pointer_name; without them, the declaration would instead describe a function returning a pointer. Assignment is straightforward: pointer_name = function_name; (the function name, without parentheses, decays to a pointer to the function—analogous to an array name decaying to a pointer to its first element). Invocation uses the same call syntax: pointer_name(args).

Function pointers enable callbacks, dispatch tables, and strategy patterns, making code more flexible and modular.

Minimal example
File: p07_j.c
Build: gcc p07_j.c -o p07_j
Run: ./p07_j

#include <stdio.h>

int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }

int main(void) {
int (*op)(int, int); /* declare function pointer */

op = add;
printf("add(2, 3) = %d\n", op(2, 3));

op = multiply;
printf("multiply(2, 3) = %d\n", op(2, 3));

return 0;
}

What you should see: add(2, 3) = 5 followed by multiply(2, 3) = 6.

Takeaway: Function pointers store the address of a function and allow the target function to be selected at run-time, providing a powerful mechanism for writing generic, extensible code.

5. Worked Examples

Example 1: Reverse an Array In-Place Using Pointers

Problem: Write a function void reverse(int *arr, int size) that reverses the elements of an integer array in-place. For input {1, 2, 3, 4, 5} the array should become {5, 4, 3, 2, 1}.

Steps:

  1. Maintain two pointers: left starting at the first element and right starting at the last.
  2. Swap the values at *left and *right.
  3. Advance left forward and right backward; repeat until the two pointers meet or cross.

Reference code
File: p07_ex1.c
Build: gcc p07_ex1.c -o p07_ex1
Run: ./p07_ex1

#include <stdio.h>

void reverse(int *arr, int size) {
int *left = arr;
int *right = arr + size - 1;

while (left < right) {
int temp = *left;
*left = *right;
*right = temp;
left++;
right--;
}
}

int main(void) {
int arr[] = {1, 2, 3, 4, 5};
int n = sizeof(arr) / sizeof(arr[0]);

reverse(arr, n);

for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");

return 0;
}

Output:

5 4 3 2 1

Why it works: The left and right pointers converge towards the centre. At each iteration the two pointed-to values are swapped via a temporary variable, using pointer dereference for both reading and writing. Because the function receives a pointer to the array (not a copy), the modifications are visible in main().

Example 2: Simple Calculator Using Function Pointers

Problem: Implement a calculator that supports +, -, *, /, and a square-root operator @. The programme reads two double operands and a character operator, then calls the appropriate function through a function pointer. Division by zero should return NAN.

Steps:

  1. Define five functions: add, subtract, multiply, divide (each taking two double parameters), and square_root (which ignores the second parameter and returns sqrt of the first).
  2. Parse the input; select the correct function and assign it to a function pointer.
  3. Invoke the function through the pointer and print the result.

Reference code
File: p07_ex2.c
Build: gcc p07_ex2.c -o p07_ex2 -lm
Run: ./p07_ex2

#include <stdio.h>
#include <math.h>

double add(double a, double b) { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }

double divide(double a, double b) {
if (b == 0.0) return NAN;
return a / b;
}

double square_root(double a, double b) {
(void)b; /* unused parameter */
return sqrt(a);
}

int main(void) {
double a, b;
char op;

scanf("%lf %c %lf", &a, &op, &b);

double (*func)(double, double) = NULL;

switch (op) {
case '+': func = add; break;
case '-': func = subtract; break;
case '*': func = multiply; break;
case '/': func = divide; break;
case '@': func = square_root; break;
default:
printf("Unknown operator.\n");
return 1;
}

printf("%.3f\n", func(a, b));

return 0;
}

Output (for input 4.5 + 2.5):

7.000

Output (for input 3.0 @ 4.0):

1.732

Why it works: A single function pointer func is assigned the address of whichever operation matches the user's input. The call func(a, b) then dispatches to the correct function at run-time. For @, only the first operand is used; the second is silently ignored. Note: the square root of 3.0 is approximately 1.732.

Example 3: Sum Array Elements Using Pointer Arithmetic

Problem: Write a function that accepts an array and its size, and returns the sum of its elements using pointer arithmetic (i.e., advancing the pointer with ++ rather than using subscript notation).

Steps:

  1. Initialise a local pointer to the start of the array.
  2. Iterate size times, adding *ptr to an accumulator and advancing ptr++.
  3. Return the accumulated sum.

Reference code
File: p07_ex3.c
Build: gcc p07_ex3.c -o p07_ex3
Run: ./p07_ex3

#include <stdio.h>

int sum_array(int *arr, int size) {
int sum = 0;
int *ptr = arr;

for (int i = 0; i < size; i++) {
sum += *ptr;
ptr++;
}

return sum;
}

int main(void) {
int arr[] = {5, 6, 7, 8, 9, 10};
int n = sizeof(arr) / sizeof(arr[0]);

printf("Sum = %d\n", sum_array(arr, n));

return 0;
}

Output:

Sum = 45

Why it works: The pointer ptr starts at the base of the array. On each iteration *ptr reads the current element, and ptr++ advances the pointer by sizeof(int) bytes to the next element. The running total accumulates all six values.

6. Common Mistakes

  1. Using an uninitialised pointer: Declaring int *p; and immediately writing *p = 10; dereferences an indeterminate address, causing undefined behaviour. Always initialise pointers—either to NULL or to a valid address—before use.

  2. Confusing * in declarations versus expressions: In the declaration int *p;, the * indicates that p is a pointer. In the expression *p = 5;, the * dereferences p. Writing int *p = 5; does not store 5 in the pointed-to location; it attempts to assign the address 5 to p, which is almost certainly invalid.

  3. Forgetting & when passing to a function that expects a pointer: Calling swap(x, y); instead of swap(&x, &y); passes copies of the integer values, not their addresses. The compiler may warn about incompatible types; heed such warnings.

  4. Arithmetic on void * pointers: The type void * carries no size information, so void_ptr + 1 is not defined by the standard (although some compilers permit it as an extension). Always cast or assign to a typed pointer before performing arithmetic.

  5. Memory leaks and dangling pointers: Calling malloc() without a corresponding free() leaks memory. Conversely, continuing to use a pointer after free() accesses memory that may already have been reallocated. Always free dynamically allocated memory and set the pointer to NULL afterwards.

  6. Declaring multiple pointers on one line incorrectly: int *a, b; declares a as a pointer and b as a plain int. To declare two pointers, write int *a, *b;.

  7. Ignoring the return value of malloc(): If malloc() returns NULL (e.g., due to insufficient memory) and the programme proceeds to dereference the pointer, a segmentation fault will occur. Always check: if (ptr == NULL) { /* handle error */ }.

7. Practice

7.1 Basic

  • P1: Print Variable Value Using Pointer
    Declare an integer n = 42. Declare a pointer to int and assign the address of n. Print the value of n using the pointer.
    Acceptance: Output is 42.

  • P2: Modify Value via Pointer Write a programme that takes an integer input and modifies its value by tripling it, using a pointer.
    Input/Output: Input 5 → Output After tripling: 15.
    Acceptance: Programme compiles without warnings; output matches for any valid integer input.

  • P3: Array Access via Pointer
    Declare int arr[] = {3, 6, 9, 12, 15};. Use a pointer and the expression *(ptr + i) in a loop to print each element.
    Acceptance: Output is 3 6 9 12 15.

7.2 Required

  • P4: Sum Array Elements Using Pointer Arithmetic
    Write a function that accepts an array and its size, and returns the sum of its elements using pointer_name++. Initialise the array with {5, 6, 7, 8, 9, 10}.
    Acceptance: Output is Sum = 45.

  • P5: Reverse Array Using Pointers
    Write a function void reverse(int *arr, int size) that reverses an array of 5 integers in-place using pointers.
    Input: 1 2 3 4 5
    Acceptance: Output is 5 4 3 2 1.

  • P6: Swap Two Values Using Function and Pointers
    Write a function void swap(int *a, int *b) that swaps two values using dereferencing. Call it from main() to swap two user inputs.
    Input: 3 5
    Acceptance: Output is 5 3.

  • P7: Memory Allocation and NULL Check
    Use malloc() to allocate space for an array of 5 integers. Check for NULL; if allocation fails, print "Memory allocation failed!" and exit with code 1. Otherwise, initialise the array with the square of each index (0, 1, 4, 9, 16), print the values, and free the memory.
    Acceptance: Output is 0 1 4 9 16.

  • P8: Pointer to Pointer
    Declare an int variable with value 42. Create a pointer p to it, then a pointer pp to p. Use **pp to print the value.
    Acceptance: Output is 42.

7.3 Optional

  • P9: Bit Mask with Pointers
    Write a programme that takes an unsigned char and a bit position (0–7). Use a pointer and a bitwise operator to toggle the bit at that position. Print the result in hexadecimal.
    Input: 65 0
    Acceptance: Output is 0x40 (65 is 0b01000001; toggling bit 0 yields 0b01000000 = 0x40).

  • P10: Simple Calculator Using Function Pointers
    Implement a calculator with add, subtract, multiply, divide, and square_root (operator @). Use a function pointer to dispatch the correct function. Handle division by zero with NAN.
    Input: 4.5 + 2.5Output: 7.000
    Input: 3.0 @ 4.0Output: 1.732
    Acceptance: Programme compiles with -lm; output matches the expected results to three decimal places.

8. Mini Task

Goal: Build a small pointer toolkit programme that demonstrates the key concepts of this chapter in a single, cohesive application. The programme shall read an array of integers from the user, display the array, reverse it in-place using a pointer-based function, display the reversed array, and finally compute and print the sum using pointer arithmetic.

Deliverables:

  • README.md: Instructions for compiling and running the programme, together with sample input and expected output.
  • src/pointer_toolkit.c: The complete source file containing main(), reverse(), and sum_array().
  • report.md: A short reflection (150–300 words) describing one difficulty encountered during implementation and how it was resolved.

9. Further Reading

  1. Kernighan, B. W. & Ritchie, D. M., The C Programming Language, 2nd edn (Prentice Hall, 1988): Chapter 5 — "Pointers and Arrays".
  2. King, K. N., C Programming: A Modern Approach, 2nd edn (W. W. Norton, 2008): Chapters 11–12 — "Pointers" and "Pointers and Arrays".
  3. ISO/IEC 9899:2018, Programming Languages — C (C18 Standard): §6.5.3.2 (Address and indirection operators), §6.5.6 (Additive operators / pointer arithmetic), §7.22.3 (Memory management functions).

10. Appendix

A. Quick Reference — Pointer Operators

OperatorNameExampleMeaning
&address-of&xYields the memory address of x.
* (expr)dereference*ptrYields the value stored at the address in ptr.
* (decl)pointer declarationint *p;Declares p as a pointer to int.
++ / --pointer increment / decrementptr++Advances (or retreats) the pointer by one element.

B. Compilation Notes

Several exercises in this chapter require the mathematics library. When using functions such as sqrt() or the constant NAN, link with -lm:

gcc p07_ex2.c -o p07_ex2 -lm

C. Memory Layout Diagram

Stack (high addresses)
┌─────────────────────┐
│ local variables, │
│ function parameters│
├─────────────────────┤
│ ... │
├─────────────────────┤
│ Heap (malloc/free) │
├─────────────────────┤
│ Global / Static │
├─────────────────────┤
│ Code (Text) │
└─────────────────────┘
(low addresses)

Variables declared inside functions reside on the stack; memory obtained via malloc() resides on the heap. Understanding this distinction is essential when working with pointers and dynamic allocation.