Understanding Arrays in C Programming
This article provides basic information and details regarding how to use arrays in C-language firmware projects.
This article provides basic information and details regarding how to use arrays in C-language firmware projects.
Supporting Information
Embedded systems frequently deal with data that naturally belongs in a group rather than in a collection of independent variables. One example that comes readily to mind is a sequence of values that corresponds to the digitized version of an analog waveform. Other examples include bytes that will be serialized and sent to a liquid crystal display, a long series of measurements that must be analyzed or transferred to a different device for processing, and a small group of ASCII characters that constitute a UART message.
In some cases it would be possible, though awkward, to store this data in individual variables—for example, ADC_value1, ADC_value2, ADC_value3, and so forth. Often, though, the use of individual variables would be completely impractical. Fortunately, the C language provides a simple and highly effective way to deal with large (or small) groups of variables. The feature that I’m referring to here is called an array.
What Is an Array?
When you declare an array, you specify the data type, the identifier for the array, and usually the number of elements in the array. The compiler reserves a section of memory corresponding to the size of the array.
Figure 1. Arrays require specification of the data type, identifier, and the number of elements.
The following are some important points to understand about arrays:
- All of the elements in the array must have the same data type. For example, you can’t have an array that consists of both char and int variables.
- The amount of memory required for the array is not necessarily the same as the number of elements. If your array has a data type of char and a length of 20, it will consume 20 bytes of memory. However, if it consists of 20 elements with a data type of long, the compiler must reserve 80 bytes of memory. A large array can claim a significant portion of a microcontroller’s RAM, so make sure that you don’t use an unnecessarily large data type. If all of the numbers in the array will be less than or equal to 255, declare the array as unsigned char, not int.
- The compiler will store the array elements in contiguous memory locations; you won’t have a few elements in one section of your memory and then the rest of the array in a completely different section. This means that you can easily check the contents of an array using the memory inspection functionality of your debug interface (though first you have to find the array’s starting address).
How to Use an Array
An array is declared like so:
unsigned char MyArray[100];
After the array has been declared, any element within the array can be read from or written to using the array identifier and the corresponding index. The index is the number that goes inside the square brackets. The compiler does the necessary memory math—i.e., it automatically accounts for the array’s data type when it determines the intended address based on the index and the address of the first element.
The following code snippet uses a for loop to fill an array with numerical values beginning at zero and ending at 99.
for (n = 0; n < 100; n++)
{
MyArray[n] = n;
}
The Zeroth Element
The first step in avoiding troublesome array-related bugs is to remember that the first element of the array is accessed using the index zero.
Figure 2. The first element for arrays is always zero.
You need to drill this into your head if you’re accustomed to other programming languages that use an index value of one for the first element, or if you simply find it very counterintuitive that the “first” element of the array is accessed using the number zero. Calling the first element the zeroth element may help you to remember this important detail.
C’s indexing convention is actually very logical if you understand the relationship between arrays and hardware. The identifier of the array corresponds to a location in memory, namely, the starting address of the array. The index is an offset; it tells the compiler which memory location to access with reference to the starting address. The memory location corresponding to the starting address is not empty; rather, it holds the data corresponding to the first element in the array. Thus, the index for the first element is zero, because there is no offset—the data for this element is stored at the starting address.
The Final Element
Another pitfall directly related to the zeroth-versus-first-element issue is that the final element of the array corresponds to an index that is equal to the size of the array minus one.
If you use the size of the array as the index, you will be accessing a memory location that is not included in the array. This could be anything—unused memory, the beginning of another array, part of a completely unrelated variable. This is a serious mistake and can lead to code that malfunctions in extremely problematic ways. Be especially careful with loops in which the index of the array increases with each iteration. The loop must be designed such that iterations cease before the index of the array reaches the number that is equal to the size of the array.
Array Initialization
Arrays, like individual variables, can be given initial values. The series of values is enclosed in curly brackets, and the individual values are separated by commas. For example:
unsigned int DAC_Config[4] = {0x10, 0x30, 0x87, 0xA1};
I mentioned above that we usually include the number of elements in an array declaration. I said “usually” because if you include a series of initial values, you can let the compiler determine the required length.
unsigned int DAC_Config[] = {0x10, 0x30, 0x87, 0xA1};
This comes in handy when you’re using an array to hold a fairly long sequence of ASCII characters, perhaps for debugging purposes or serial-port communication.
unsigned char DataReady_Message[] = "New thermocouple data is available";
Notice that the characters are enclosed in double quotation marks and that curly brackets are not needed.
A sequence of ASCII characters is referred to as a string, and it is common practice to terminate a string with the ASCII null character. With a string initialization such as the one shown above, the array is one element longer than you would expect, because the compiler automatically includes a null character at the end of the array. The diagram below represents the memory contents created by the array declaration shown in the code snippet.
unsigned char MyString[] = "TxRDY";
Figure 3. A representation of the memory contents created by the array declaration shown in the above code snippet.
Conclusion
I think that this article gives you enough information to get started with C-language arrays, but there is certainly more that could be said. Make sure to keep an eye out for the next article, which will cover C pointers. This topic is interesting in itself, but it is also a good way to reinforce your understanding of arrays.
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
Gee, Robert, I would have liked to see multidimensional arrays, at least two dimensional arrays mentioned, as I struggled to get my head around the idea. As these are commonly used for say timestamping a sequence of measurements.
Pogo