Pointers

Normally, a variable contains a direct reference to a specific value.

Pointer variables are variables that contain memory addresses as their values.

Pointers store the address of a variable with a particular value, establishing an indirect reference.

Examples:

#include <stdio.h>
int main()
{
  int *p = NULL;
  int x = 42;
  p = &x;
  printf("%d", *p); // Dereferencing of the pointer
  return 0;
}
#include <stdio.h>
int main()
{
  int *p = NULL;
  int x = 42;
  p = &x;
  x += 100;
  printf("%d", *p);
  return 0;
}
#include <stdio.h>
int main()
{
  int *p = NULL;
  int x = 42;
  p = &x;
  x += 100;
  *p = *p + 200;
  printf("%d", *p);
  return 0;
}

Referencing and Dereferencing

Addressof Operator: &

Dereference Operator: *

int y = 2;
printf("%x \n", *y);

—will just result in a syntax error.

Note: Most compilers will actually cancel out any * and & operators next to each other, as they are complements, regardless of their order, so *&y and &*y would be valid ways to access y.

Void Pointer

void * is a pointer to an unspecified data type.

int a = 4;
void *p;
int *c;
p = &a;
c = (int *)p; // Here we assign the value of pointer p to the point c, after casting it to be able to do so

const and Pointers

const: Qualifier that indicates that the value of a variable should not be modified.

Protecting Pointer Arguments

Pointer arguments can be protected against accidental changed by using the qualifier const.

Example: Making pointer arguments const

// Disallow modifying the value at the pointer's address,
// Allow changing the pointer's address.
void f (const int *p) // POINTER TO CONSTANT DATA
{
  int j;
  *p = 0; // Not permitted
  p = &j; // Permitted
}
// Allow modifying the value at the pointer's address,
// Disallow changing the pointer's address.
void g (int * const p) // CONSTANT POINTER TO DATA
{
  int j;
  *p = 0; // Permitted
  p = &j; // Not permitted
}
// Disallow modifying the value at the pointer's address,
// Disallow changing the pointer's address.
void h (const int * const p) // CONSTANT POINTER TO CONSTANT DATA 
{
  int j;
  *p = 0; // Not permitted
  p = &j; // Not permitted
}

Four Ways to Pass a Pointer to a Function:

  1. Non-constant pointer to non-constant data.
  2. Non-constant pointer to constant data.
  3. Constant pointer to non-constant data. (aka: reference)
  4. Constant pointer to constant data.

Note: In most cases at least one const should be used. It’s very rare that you want to modify both the pointer’s value and address.

Reference

Reference: A constant pointer.

int a[3];
int b[5];
a = &b; // Invalid, because a is a constant pointer! (aka: reference!)
a = a + 1; // Invalid, because a is a constant pointer!

Pointer Arrays

Array: Defines a contiguous location of memory for its elements.

char a[] = "A string is just an array of chars!"
printf("%c", a[5]) // Prints "i"

When you subscript an array expression (a[5]), C is actually computing the offset from the address of the first element in the array (each data type takes up a certain amount of memory) and dereferencing the result.

General Formula: addr(ptr + i) = addr(ptr) + sizeof(type) * i

&a[0] and a both actually point to the same location in memory: The start of the contiguous block of memory assigned to the array.

#include <stdio.h>
#include <string.h>
int main()
{
    char a[] = "A string is just an array of chars!";
    int SAME = memcmp(&a[0], a, sizeof(char));
    if (SAME == 0)
        printf("&a[0] and a point to the same address!");
}

C places the NULL terminal (\0) at the end of character arrays (null-terminated strings) so that the end of the string can be known.

#include <stdio.h>
int main()
{
    char a[] = "A string is just an array of chars!";
    // Method A: Printing each character by using array syntax
    int index = 0;
    while (a[index] != '\0')
    {
        printf("%c", a[index]);
        index++;
            
    }
    puts("");
    // Method B: Printing each character by pointer arithmetic
    char *p = a;
    while (*p != '\0')
    {
        printf("%c", *p);
        p++;
    }
}

Pre-and-Post Increment/Decrement

Like for awk, prefix and postfix only changes what is returned by the operator. The operator will still increment or decrement the target variable just fine, and which one you use only matters if you’re using the return value.

Incrementing/Decrementing Pointers

“Does it hurt when I do that?”

ExpressionMeaning
*aPtr++ or *(aPtr++) or (*aPtr)++Post-increment aPtr. Returns *aPtr before increment
*++aPtr or *(++aPtr)Pre-increment aPtr. Returns *aPtr after increment
++*aPtr or ++(*aPtr)Pre-increment *aPtr. Returns *aPtr after increment

Pointer Drawbacks

Note: Unlike C++, C has no smart pointers.

Dangling Pointers

Dangling Pointers: Pointer that contains the address of a heap-dynamic variable that has been deallocated.

Example: Dangling pointer

int *x, *y;
x = malloc(sizeof(int));
*x = 1;
y = x;
free(x);
printf("%d", *y); // Will print garbage
int *x, *y;
x = malloc(sizeof(int));
*x = 1;
y = x;
y = NULL;
free(x);
printf("%d", *y); // Will segfault

Memory Leaks

Memory Leak or Garbage Creation: Lost-heap dynamic variables.

Example: Memory leaks

int *x = malloc(100);
x = NULL;
int *x = { 1, 2, 3 };
int *y = { 4, 5, 6 };
x = &y;