From the perspective of an electrical engineer who writes firmware for embedded systems, pointers are not a necessary tool. However, I'm going to cover them now rather than later in this series because they are closely related to arrays, which we discussed in the previous article. Also, pointers help to reinforce our understanding of the relationship between code and hardware.
I’ve written quite a bit of firmware in my life, and I have only a few stray memories of using pointers. I’m glad that I understand them, though, because when a situation seems to call for pointers, I would much rather use pointers than simply default to an inferior solution.
Supporting Information on C Programming
What Is a Pointer?
A pointer is a variable. Like other variables, it has a data type and an identifier. However, pointers are used in a way that is fundamentally distinct from the way in which we use “normal” variables, and we have to include an asterisk to tell the compiler that a variable should be treated as a pointer. Here are two examples of pointer declaration:
char *RxByte_ptr; int *ADCValue_ptr;
The identifier does not need to contain characters (such as “ptr”) that flag the variable as a pointer. However, I highly recommend this practice. It will help you to keep your thoughts more organized, and if you have all your pointers labeled in this way, it will be easier for other engineers to understand your code.
What Does a Pointer Do?
It points. More specifically, it points at another variable’s data, or at data that is stored in memory but not associated with a variable.
We usually think of a variable as something that stores data, and by “data” we mean information that will be used in a computation, or sent to another device, or loaded into a configuration register, or used to control LCD pixels. A pointer is a variable, but it isn’t used to store this type of data. Rather, a pointer stores a memory address.
Temperature data is stored in a variable located at memory address 0x01, and the blue variable is a pointer that holds the address at which the temperature data is stored.
Perhaps this is the point at which some people start to be a bit confused, and I think that this occurs because it is easy to lose sight of the physical reality of a processor’s memory. A block of memory is a collection of digital storage cells that are organized into groups. In the case of an 8-bit processor, each group of storage cells corresponds to one byte. The only way to distinguish one group from another is by means of an address, and this address is simply a number. The pointer is a variable that stores a number, but this number is interpreted as an address—i.e., as a value that specifies an exact location in memory.
Let’s reinforce this concept with a brief analogy. Imagine that I am standing in a library and someone walks up to me and says, “Who is Richard the Lionheart?” If I respond by saying, “The king of England from 1189 to 1199,” I am like a normal variable. I am providing the information, the data, that the person wants. In contrast, if I respond by pointing at a book entitled Monarchs of Medieval England, I am like a pointer. Instead of providing the desired data, I am indicating exactly where that data can be found. I still contain useful information, but the information is not the fact itself—it is the location where the person can access that fact.
Understanding Pointer Data Types
As you may have noticed in the examples shown above, pointers are declared with a data type. Perhaps this contributes to the difficulty of understanding what a pointer really is. If a pointer is simply a number that corresponds to the address of a memory location, how do the different data types come into play? For example, if your microcontroller has 4 kB of RAM, how could you ever have a pointer with the data type of char? The maximum value of an unsigned char is 255; what happens if this pointer must point to a variable that is located at memory address 3000?
The key to understanding this issue is the following: The data type of a pointer does not indicate how many bytes are used to store its value. Rather, the number of bytes used to store the value of a pointer corresponds to the number of memory addresses that must be accessed, regardless of the pointer’s data type. Furthermore, the size of a pointer is determined by the compiler and is not directly visible to the programmer.
Consider the following diagram:
Let’s say we’re using a pathetic microcontroller that has only 11 bytes of RAM. The range of values offered by an 8-bit number is 0 to 255, so one byte of memory is more than adequate for representing all the possible memory locations in this device.
The diagram emphasizes the fact that even a variable declared as long can be accessed via a one-byte pointer. The blue variable is a pointer that holds the address of the 32-bit variable Seconds_Cnt. This variable uses four bytes of memory, but the address of the variable (which in this example corresponds to the least significant byte) will always be a number equal to or less than 0x0A. The pointer must be declared with the data type long because it is used in conjunction with long variables, but the pointer itself consumes one byte of memory, not four.
To Be Continued...
One article just doesn’t suffice for a topic as exhilarating as pointers. You now know what a pointer is and the basic functionality that it provides in the context of C programming. In the next article, we’ll be able to dive into the action, i.e., how to actually use pointers in your firmware projects.
C Language Series
- Introduction to the C Programming Language for Embedded Applications
- Understanding Variables in C Programming
- Understanding Arrays in C Programming
- Pointers in C Programming: What Is a Pointer and What Does It Do?
- What Are Functions in C Programming?
- How to Incorporate Functions into Embedded Firmware
- How to Use Pointers in C-Language Firmware
- Five Tips for Using Functions in C-Language Firmware