Introduction
Pointer in C is probably the most interesting but yet terrifying feature about this programming language. It is a special type of variable that stores only the memory address
of another variable. A memory address is normally expressed in hexadecimal form. If you are not familiar with it, you can refer to this wiki page here. It essentially “points” to the memory location of a data variable, thus the name. Pointer in C offers a way to indirectly manipulate and access data by referencing or dereferencing a memory address rather than working with the actual data. This is an advanced programming technique and has several advantages in terms of performance. In this blog, I will explain how C pointer works and the best practices with pointers.
Facts About Pointers
- A pointer
references
a location in memory - Obtaining the value pointed by a pointer in memory is called
dereferencing
the pointer. - You must use a pointer to perform dynamic memory allocation. For example:
int * a = NULL; a = (a *) malloc (sizeof(int));
- You need to master the art of pointer to become an advanced C programmer.
- A variable can be passed to a function by value. For example:
int a = 10; somefunction(a); /* pass by value - the value of 10 is copied inside somefunction() */
- A variable can also be passed to a function by reference using the ampersand operator (&). For example:
int a = 10; somefunction(&a); /* pass by reference - the address of variable a is copied inside somefunction() */
Reference a Pointer
Reference a C pointer means to declare a variable as a pointer
type and points to some reference. This reference can be another variable’s address, NULL (point to nothing), or a function (also known as function pointer). We can use the =
operator (same operator to give a variable value) to set a reference to point to.
Syntax to Reference a Pointer
This syntax declares a basic pointer:

Take the Memory Address of a Variable
We can use the ampersand (&
) operator to get the memory address of a variable and assign it to a pointer like below:

Dereference a Pointer
Dereference a pointer basically means “value pointed by”, which returns the value it points to rather than its address. Based on the data type of the pointer being dereferenced, the compiler will evaluate the value based on the storage size of the data type. The example above illustrates a pointer of type int
, which means that the compiler will “evaluate 4 bytes” from where this pointer points at and treat them like an integer value. Dereferencing can be done using the asteroid (*) operator. Consider this example:

The example above first prints the pointer myptr
as a pointer address and then prints the “value pointed by” myptr
using the dereferencing operator. It will produce this output below:
myptr references to the memory address of 0x4d03fd0c
myptr dereferences to the value of 90
Pointer Arithmetic
Arithmetic operations with a pointer? What?
Yes, it is 100% doable. A pointer is just a number after all, a very big one. So, it is possible to do arithmetic operations on it. To add, substract, multiple, bit-wise operations… you name it. This basically changes the reference
that the pointer points to.
myptr = myptr + 10; myptr = myptr - 20; myptr++; myptr--; /* ... and many more arithmetic operations */
Be Careful
Arithmetic operations on pointers are handy, but be very, very careful with this. It is possible to make a pointer reference
to any memory address. If you try to dereference
a pointer that points to a memory segment that you are not allowed to operate on, the operating system will terminate your application due to memory violation (For example, a Segmentation Fault on Linux based platform).
This is also known as an “application crash”.
Function Taking C Pointer as Argument
One of my favorite use cases with pointers is to pass them in functions as arguments, which act also like return values. Yes you heard this right, arguments can hold return values using C pointers, and you can have more than on. This is often set up with a integer in the original return value to indicate if the function is executed successfully or not. Pointer argument is done by passing the arguments by reference
and have the function modify the values pointed by the pointers using dereferencing operator (*
). Consider this example:
/* * this function takes the input "in_month" and returns how many days * in that month in "out_NumDays" and the name of the month in "out_monthName" * returns 0 if input is valid -1 otherwise. */ int pointerArgumentExample (int in_month, int * out_numDays, char * out_monthName) { if (in_month < 0 || in_month > 12) return -1; if (!out_numDays || !out_monthName) return -1; switch(in_month) { case 1: { *out_numDays = 31; strcpy(out_monthName, "January"); break; } case 2: { *out_numDays = 28; strcpy(out_monthName, "February"); break; } case 3: { *out_numDays = 31; strcpy(out_monthName, "March"); break; } case 4: { *out_numDays = 30; strcpy(out_monthName, "April"); break; } /* you may add more case here... */ default: { *out_numDays = -1; strcpy(out_monthName, "Invalid"); return -1; break; } } return 0; } int main (void) { int ret = -1; int monthDays = 0; char monthName[64] = {0}; ret = pointerArgumentExample(2, &monthDays, &monthName[0]); if (ret) printf("invalid input\n"); else printf("monthDays = %d, monthName = %s\n", monthDays, monthName); }
The pointerArgumentExample()
above takes 3 arguments, 2 of which are pointers (out_numDays
and out_monthName
). These 2 arguments are references to the variables monthDays
and monthName
declared in the main function and the pointerArgumentExample
function is responsible for filling their values. The argument in_month
is passed by value to indicate the month that the caller is interested in. Changing this value within the function does not affect the original value at the caller.
The function first does some error checking to make sure in_month
is within valid range. The function returns -1 on invalid ranges:
if (in_month < 0 || in_month > 12) return -1;
Then the function checks the validity of the pointers, which must point to a valid address that is not NULL. The function returns -1 on NULL pointers.
if (!out_numDays || !out_monthName) return -1;
The return value of -1 indicates to the main function that pointerArgumentExample()
has encountered an error and the values of monthDays
and monthName
are most likely invalid. The return value of 0 indicates to the main function that pointerArgumentExample()
has filled monthDays
and monthName
with valid values.
pointerArgumentExample()
uses a switch
statement, dereferencing operator *
and standard strcpy()
function to modify the values pointed by the pointers out_numDays
and out_monthName
.
switch(in_month) { case 1: { *out_numDays = 31; strcpy(out_monthName, "January"); break; } ... ... ... ... ... ... }
Passing Reference from the Caller
The main function (the caller) declares 3 variables: ret
, monthDays
and monthName
:
ret
is to hold the return value (error code) from the function. In the above example, 0 means successful, otherwise failure.monthDays
is a local variable that is to hold number of days of a month and it is passed topointerArgumentExample()
as an address using the ampersand (&
) operator.monthName
is also a local variable that is to hold the name of the month as achar array
. The address of the first element of this array is passed topointerArgumentExample()
using the ampersand (&
) operator as well.
At the main function, the local variables, monthDay
and monthName
will only have values when pointerArgumentExample()
returns zero (success), and therefore it prints their values out on success.
Advantage of Such Setup
Functions can result in error in some cases, and normally we use a return value of int
to indicate different error codes
. Returning an error code is a very good practice when writing a function, so the caller can learn about reasons of failure and handle it accordingly. If the function also needs to return additional values back to the caller, they can choose to use C pointers in the function arguments to hold these values at the caller.
Function Returning a C Pointer
A function returning a pointer is a common but dangerous use case that requires a programmer to pay extra attention. A function cannot return any pointer reference as it likes, it is responsible making sure the pointer address is valid and safe to use. This normally involves the scope
of a variable and dynamic memory allocation.
Consider this example:
int * pointerReturnExample(void) { int a = 1000; return &a; } int main (void) { int * myintptr = NULL; myintptr = pointerReturnExample(); printf("pointerReturnExample returns %d\n", *myintptr); return 0; }
The above example will NOT work because the variable a
inside pointerReturnExample
is a local variable that lives only within that function. It resides in local stack memory
only accessible within the function. When this function returns, the memory of this local variable is destroyed. This local memory address has no use at the main function, therefore the code will not work.
Consider another example:
int * pointerReturnExample(void) { int * b = NULL; b = (int *)malloc(sizeof(int)); if(b) { *b = 1500; } return b; } int main (void) { int * myintptr = NULL; myintptr = pointerReturnExample(); printf("pointerReturnExample returns %d\n", *myintptr); return 0; }
The above example will work fine because the variable b
inside pointerReturnExample
points to a piece of heap memory
reserved using the malloc()
function call. This memory is valid throughout the life time of the program even after this function returns. (Unless this piece of memory is explicitly free()-ed
. Because the heap memory is valid, the main function (the caller) is able to operate or read the values referenced by this pointer.
The Void Pointer
The void
keyword normally means nothing
when used in a function’s declaration. A function taking void
in argument means it takes no argument; a function returning void
means it returns nothing. However, when void
is used with a pointer (void *), it means this reference can be of any data type. You are responsible for casting
the void pointer to a suitable data type before you can work on it.
Consider this example below:
typedef struct user { int userid; char username[64]; unsigned int age; char occupation[64]; } UserInfo; void voidPointerExample(void * input1, void * input2) { char * charptr = (char*) input1; /* input1 shall be cast to a string (char*) before we can work with it */ UserInfo * userptr = (UserInfo*) input2; /* input2 shall be cast to UserInfo struct before we can work with it */ printf("charptr = %s\n", charptr); printf("userptr.userid = %d\n", userptr->userid); } int main (void) { UserInfo myuserinfo; voidPointerExample("kasjhdkajshdjk", &myuserinfo); }
The voidPointerExample()
function above takes 2 arguments, each having data type of void *
, which means they can be any data type pointers. It is the programmer’s responsibility to know what data type pointer the void *
can potentially represent. In the example, input1
is cast to a string (char*) while input2
is cast to UserInfo struct because this is just an example. In a real function working with void pointer, you may have additional function arguments that tells the function what data type to cast the void pointer to.
Void pointer is most useful when a function works with multiple data types, but you do not wish to declare an argument for each data type as it is inefficient. Instead, you can use a void pointer to represent any data type and have a selector argument to select a data type. For example:
enum mytype { TYPE_INT, TYPE_CHAR_PTR, TYPE_SHORT }; void voidPointerExample(void * mydata, int type) { switch(type) { case TYPE_INT: { int * myintptr = (int *) mydata; printf("input is integer: %d\n", *myintptr); break; } case TYPE_CHAR_PTR: { char * mycharptr = (int *) mydata; printf("input is string: %s\n", mycharptr); break; } case TYPE_SHORT: { short * myshortptr = (short *) mydata; printf("input is short: %d\n", *myshortptr); break; } default: { printf("unsupported data type\n"); break; } } } int main (void) { int myintdata = 200; voidPointerExample(&myintdata, TYPE_INT); voidPointerExample("some random read only string", TYPE_CHAR_PTR); short myshortdata = 100; voidPointerExample(&myshortdata, TYPE_SHORT); }
The Function Pointer
A pointer can also reference to a existing function as long as you know its return value and the list of arguments. This is particularly handy if a function needs to call multiple other functions at different conditions and you do not want to write a huge if statement
containing each case. A function pointer can reduce amount of code writing and make the code more organized.
We can declare a function pointer following this general syntax:
Syntax:
[return type](*[function pointer name) ([list of argument's data types]);
Example:
void (*handleError_ptr) (int, char*);
The above example “declares” a function pointer that shall point to another function that takes 2 arguments having data types of int
and char*
and returns void
. Please note that we are just declaring it, we have not pointed handleError_ptr
to any function yet. Consider the example below where we point it to a real function using the ampersand (&
) operator.
void handleError(int errorCode, char * errorMsg) { printf("handling error code = %d and msg = %s\n", errorCode, errorMsg); /* add your own logic here to handle different errors */ } void functionPointerExample(void) { /* declare a function pointer */ void (*handleError_ptr) (int, char*); /* point it to handleError function */ handleError_ptr = &handleError; /* invoke the function via the function pointer */ handleError_ptr(-2, "file is corrupted"); } int main(void) { functionPointerExample(); return 0; }
Ok, so in the above example, we basically declare a function pointer and call the function via this pointer. It is not very useful, isn’t it? We could have just called the function directly right? True, but the versatility of a function pointer is that it can be passed to another function as an argument and have the function call it at certain condition. For example:
void doSomething( void (*errorHandler)(int, char*) ) { /* * if an error is encountered, it will call "errorHandler" * function pointer supplied by the caller to handle the error */ int errorfound = 1; if(errorfound) { errorHandler(-3, "another error"); } } void handleError(int errorCode, char * errorMsg) { printf("handling error code = %d and msg = %s\n", errorCode, errorMsg); /* add your own logic here to handle different errors */ } void functionPointerExample(void) { /* declare a function pointer */ void (*handleError_ptr) (int, char*); /* point it to handleError function */ handleError_ptr = &handleError; /* call doSomething function and give it the function pointer */ doSomething(handleError_ptr); } int main(void) { functionPointerExample(); return 0; }
In the example above, we have another function called doSomething()
which takes an argument of a function pointer. It shares a very similar syntax as declaring a function pointer. errorHandler()
function pointer contains a reference to the actual function called handleError()
and doSomething()
can invoke this function indirectly by calling the function pointer instead. This is handy in the case where this function changes at different conditions. So, we can simply change the reference of where the function pointer points to instead of manually writing out all possible function calls.
Related Posts
- A Closer Look at C Data Types and Variables
- Create Custom Data Types With the C Struct
- Create User Interactions with Printf and Scanf
- Common Usages of The C Switch Statement
- The C Scope is More Important than a Variable

Hi, this is Cary, your friendly tech enthusiast, educator and author. Currently working as a software architect at Highgo Software Canada. I enjoy simplifying complex concepts, diving into coding challenges, unraveling the mysteries of software. Most importantly, I like sharing and teaching others about all things tech. Find more blogs from me at highgo.ca
Pingback: A Deeper Look at Computer Memory - techbuddies.io