Overview
Embedded software designers are required to access specific addresses within the microprocessor all the time. We use addresses to access peripheral devices, configure the processor, and a variety of other operations. In the C language, a pointer is used to access data at a specific address. Here we will examine the basic syntax of how to allocate and use pointers.
Declaring a pointer
A pointer is declared using the asterix character and indicating the type of data the pointer points to. This allows the compiler to determine the appropriate assembly instructions required for accessing the address stored in the pointer. If you are using a uint16_t pointer to write to a memory location, the compiler knows to use STRH commands. If you use a uint32_t pointer, the compiler knows to use STR commands.
uint32_t *unsignedWordPtr; uint16_t *unsignedHalfWordPtr; Node *myNode;
In the example above, how many bytes are required to allocate for each pointer type? The answer is 4 bytes for all of the pointers. All pointers in the Cortex-M architecture are 4-bytes in length because all addresses are 32-bits.
Initializing a Pointer
When instantiated, a pointer points to NULL or address 0x00000000. NULL means that the pointer does not point to anything. In order to initialize a pointer, we need to set the pointer to a valid memory address. We can do this either by assigning the pointer to the address of a local/global variable or my dynamically allocating memory and setting the pointer to the address returned by malloc.
//Assigning a pointer to a static variable uint32_t var1; uint32_t *myPtr = &var1; //Assigning a pointer to a dynamic memory address Node *myNode; // Allocates memory from the heap // for on Node Struct myNode = malloc(sizeof(Node));
If you try to access the memory of an uninitialized pointer, bad things happen. You are essentially trying to read an invalid memory location. The result is that the microprocessor will issue a fault, your program terminates, and the processor is stuck in the fault handler until the processor is restarted.
De-referencing a Pointer
When you want to change the value stored at a memory address, we de-reference the pointer. This does not change the address the pointer contains, rather we change the value found at that address. De-referencing is indicated with a *
uint32_t var1; uint32_t *myPtr = &var1; // the same as var1=1; *myPtr = 1;
When dealing with pointers to structs, we will access the fields within the struct using “->” to de-reference the field.
Node *myNode = malloc(sizeof(Node)); myNode->size = 0; myNode->next = NULL;
Pointer Arithmetic
Pointer arithmetic is the process of adding an offset to the pointer to access a different memory location.
When we add a constant to a pointer, the resulting memory address is dependent on the size of the data being pointed to. When we add positive 1 to a 32-bit variable, we actually increment the memory address by 4 bytes. If we add +1 to a 16-bit variable, we increment the address by 2 bytes.
uint32_t *ptr1; uint8_t *ptr2; // Allocate space for 4 32-bit variables ptr1 = malloc(4 * sizeof(uint32_t) ); // if ptr1 = 0x20000000, ptr <= 0x20000004 after // this instruction ptr1 = ptr1 + 1; // Allocate space for 16 8-bit variables ptr2 = malloc(16 * sizeof(uint8_t) ); // if ptr2 = 0x30000000, ptr <= 0x30000001 // after this instruction ptr2 = ptr2 + 1;
If we are adding constants to a structure, adding positive 1 to the pointer will advance the pointer the total number of bytes for all the fields contained in the struct.
typedef struct { uint32_t v1; uint32_t v2; uint32_t v3; uint8_t *ptr; } genStruct; genStruct *ptr1; genStruct structArray[10]; ptr1 = structArray; // if ptr1 = 0x20000000, after this instruction // ptr1 = 0x20000000 + sizeof(genStruct) ptr1 = ptr1 + 1;
Examples
// Create a pointer to a character char *string; // Allocate 27 characters string = malloc(27); // Initialize the string from a-z for(i=0; i<26; i++) { string[i] = 'a' + i; } // Set the last character in the string to 0 to end the string string[26] = 0; // Return the memory to the heap. free(string);
This example is functionally the same as above, but illustrates a different way to access data using pointers.
int i; // Create a pointer to a character char *string; char *currentChar; // Allocate 27 characters string = malloc(27); currentChar = string; // Initialize the string from a-z for(i=0; i<26; i++) { *currentChar = 'a' + i; currentChar++; } // Set the last character in the string to 0 to end the string currentChar = 0; printf("** %snr",string); // Return the memory to the heap. free(string);