The blog post introduces more concepts related to pointers.

NULL Pointers
When declaring a pointer without initializing it, we might think it points to nothing. However, this is not the case.
int main () {
int *p;
printf("%p\n", p); // => 0x16 (or some other garbage address)
return 0;
}
To ensure that a pointer is not pointing to any address yet, we can use NULL pointers.
int main () {
int *p = NULL;
printf("%p\n", p); // => 0x0
return 0;
}
When is it useful? When we request too much memory with malloc
, calloc
, or realloc
,
they return NULL
and we can handle that by checking if the returned pointer is NULL
.
We can also use NULL pointers to prevent dangling pointers, where pointers still point to
the same address after freeing up the memory.
int main () {
int *p = malloc(sizeof(int) * 10);
if (p == NULL) {
printf("Pointer is NULL");
} // Check if p is NULL after malloc
free(p);
p = NULL; // Prevent dangling pointers
return 0;
}
The dangling pointers should be avoided because accessing them after the memory has been freed up (which happens in a large codebase) can cause unpredictable behaviors and segmentation faults.
Void Pointers
We have always been setting types for pointers to avoid mistakes. This is generally the best practice, but there are cases where you want to be more ambiguous about the data types of the variables the pointers are pointing to. In such cases, void pointers come into play.
int main () {
int a = 5;
float b = 3;
void *p; // Void pointer
p = &a;
p = &b; // No warning
return 0;
}
One way void pointers are helpful is when we want to create a function that takes a pointer of any type and/or
returns a pointer of any type. malloc
, calloc
, and realloc
are examples of such functions. Remember that you
cannot dereference a void pointer directly, and you cannot perform pointer arithmetic with a void pointer
without casting it to another type.
Function Pointers
Just as we can set up a pointer to a variable, we can set up a pointer to a function, as shown below.
void function (int x) {
printf("%d\n", x);
}
int main () {
// Function pointer
void (*pFunction) (int);
pFunction = function;
int a = 5;
pFunction(a); // "5"
return 0;
}
Why is this useful? Function pointers are useful when you want to pass a function as an argument to another function, which we refer to as callback functions.
// Callback Function
// Celcius
int freezing_C (float a) {
if (a <= 0) {
return 1;
} else {
return 0;
}
}
// Fahrenheit
int freezing_F (float a) {
if (a <= 32) {
return 1;
} else {
return 0;
}
}
void is_freezing ( int (*freezing)(float), float a ) {
if (freezing(a) == 1) {
printf("Freezing!");
} else {
printf("Not freezing.");
}
}
int main () {
char scale;
float temperature;
printf("Select temperature scale with F or C: ");
scanf("%c", &scale);
printf("Temperature: ");
scanf("%f", &temperature);
// Passing function as an argument
if (scale == 'F') {
is_freezing(freezing_F, temperature);
} else {
is_freezing(freezing_C, temperature);
}
return 0;
}
Similar to void pointers, we cannot perform pointer arithmetic with function pointers, as it does not make sense in this context.
Exercises
From this article, there will be an exercise section where you can test your understanding of the material introduced in the article. I highly recommend solving these questions by yourself after reading the main part of the article. You can click on each question to see its answer.
Resources
- Portfolio Courses. 2023. NULL Pointer | C Programming Tutorial. YouTube.
- Portfolio Courses. 2023. Void Pointers | C Programming Tutorial YouTube.
- Portfolio Courses. 2023. Dangling Pointers | C Programming Tutorial YouTube.
- Portfolio Courses. 2022. Function Pointers | C Programming Tutorial YouTube.