This short video will explain what is a pointer in the C programming language.
First, let's review what is a memory address.
We can view computer memory as an array of bytes, and each byte has a unique address.
That is why we call computer memory is byte addressable.
In other words, the smallest data object that can be addressed is a byte.
For ARM Cortex-M microprocessors, each memory address has 32 bits, which can address a total
of 4 GB.
A data object may take multiple bytes in memory.
For example, a word occupies four bytes in memory.
There are two different formats to store a word.
When storing a word in little endian format, the most significant byte is stored at a high
address, and the least significant byte is stored at a low address.
On the other hand, when storing a word in big endian format, the most significant byte
is stored at a low address, and the least significant byte is stored at a high address.
What is a pointer?
The value of a pointer is simply the memory address of some variable stored in computer
memory.
If a variable occupies multiple bytes in memory, the address of this variable is defined as
the lowest address of all bytes it occupies.
Let me give you an example.
Suppose a 32-bit integer takes these four bytes in memory, starting from 20000004 to
20000007 in hex.
Then, the address of this integer is 20000004, regardless of the endian.
No matter what endian format is used, little endian or big endian, the memory address of
this 32-bit integer does not change.
What is the basic syntax to define pointers in C?
The basic syntax is: type, asterisk (*), pointer name.
Here are four examples.
These examples define a pointer, which can hold the address of the memory location, that
contains an integer, a float, a double, and a character, respectively.
For C pointers, there are two important operators, including reference operator, and dereference
operator.
Let's look at reference operator first.
The expression, ampersand x, returns the memory address of variable x.
The ampersand is called reference operator or address-of operator.
The expression, asterisk p, returns the value of the variable to which the pointer p points.
The asterisk (*) is called dereference operator.
The symbol, asterisk, has different meaning in a declaration statement and in an expression.
Let me show you how the reference and dereference operators are used.
Suppose we declare an integer variable x.
Assume this integer variable is stored in memory address 20000004 in hex.
The variable x has an initial value of 1.
Then, we define a pointer variable named ptr.
The pointer ptr can point to a memory location, at which an integer is stored.
Suppose the pointer ptr is stored at the memory address 20000008 in hex.
When we declare a pointer variable, its content is not initialized.
In other words, the content of the pointer variable is not a valid memory location.
We can initialize a pointer variable by using the reference operator.
Specifically, we can use the reference operator to obtain the memory address of a variable,
and assign the address to a pointer variable.
This statement assigns the memory address of variable x to the pointer variable ptr.
Let's take a look at the memory content.
As you can see, the 32-bit value 20000004 in hex is stored at the memory location 20000008.
In fact, this value is the memory address of variable x.
As result, ptr points to x.
As illustrated in this statement, the reference operator generates a pointer that points to
a variable.
This expression "*ptr" represents the value pointed by ptr.
This C statement stores 5 to the memory location pointed by ptr.
As result, we indirectly set the value of variable x to 5.
Here is the summary.
The reference operator works on variables, and it can be read as "address of".
Therefore, "& var" can be read as address of var.
At the same time, the dereference operator works on pointers, and it can be read as
"value pointed by".
Therefore, "* ptr" can be read as value pointed by ptr.
What will happen if we increase the pointer by 1?
Suppose the pointer ptr is stored at the memory address 20000000 in hex.
The content of this pointer is 20000004 in hex.
Here, we assume each word is stored in memory in little endian format.
If we execute ptr++, what is the content of ptr?
Is it 20000005 in hex?
The answer is: it depends.
Suppose ptr points to an array of characters.
Each character takes one byte in memory.
The pointer ptr points the beginning of the array.
In other words, ptr points to a[0].
Let's see what will happen if we run ptr++.
If we run ptr++, the pointer points to the next element in this array.
the content of ptr is incremented by the size of a character.
Because each character takes one byte, the value of ptr is incremented by 1.
Therefore, in this case, the value of ptr is 20000005 in hex.
Now, let's look at a different example.
Suppose ptr points to the beginning of an integer array.
If we increment the value of ptr by 1, will the value of ptr be 20000005?
The answer is no.
The value of ptr will be 20000008, which is the memory address of the array element, a[1].
When we run ptr++, the value of ptr will be incremented by 4, because each integer takes
4 bytes in memory.
Let's take a closer look at pointer and array.
Suppose we declare a pointer ptr, and it is stored at memory address 20000000.
We then define an integer array a[5].
The array is stored in memory location starting at 20000004.
This C statement sets the first element to 7.
This example is to show that, in C language, the array name is a pointer to the first
element in the array.
Here, we set ptr to the memory address of a[0].
As you can see from the memory content, the 32-bit value stored at the memory address
20000000 is 20000004, which is the memory address of a[0].
What does this statement do?
Pay attention.
ptr + 2 is not to add the value of ptr by 2.
In fact, it adds ptr by 2 times, size of integer.
Each integer takes four bytes, Therefore, it adds ptr by 8, pointing to the third element
in this integer array.
The increment operator ++ and the decrement operator - - have greater precedence than
the dereference operator, asterisk.
Therefore, the following two expressions are different.
In the first expression, read or write the value pointed by the pointer.
Then, increment the pointer, pointing to the next element in an array.
For example, this C statement is equivalent to the following two C statements.
In the second expression, the value pointed by the pointer ptr is incremented by 1.
The content of the pointer does not change.
For example, this C statement is equivalent to the following two C statements.
So far, we have discussed pointers that point to variables, whose memory addresses are not
directly determined by software code.
How to build a pointer, which points to a given specific memory address?
For example, the memory address of the output data register (ODR) for GPIO Port A is determined
by its designer.
How to build a pointer to it so that software can easily access the value of the output
data register.
Here, I give three examples, which sets GPIO pin PA5 to high.
In the first example, we define a macro, which actually is the memory address of the output
data register for port A. First, we cast this constant memory address to a pointer, which
can point to an unsigned 32-bit integer.
Then, we use dereference operator to access the value pointed.
In the second example, we define a pointer by using this macro.
This macro casts the memory address to a pointer.
Therefore, in the code, we can directly use dereference operator.
In the third example, we incorporate the dereference operator into the macro.
As result, software can directly use the de-referenced pointer to access the memory.
The last approach has been used widely.
Make sure to add the keyword "volatile" if we want to force compiler to read a new value
each time.
The keyword "volatile" has been discussed in details in the textbook.
It prevents compiler from making false optimization during the compiling process.
In the device header file of STM32 Cortex-M processors, the memory addresses of peripherals
are cast to a struct.
For example, the base memory address of GPIO port A is 48000000 in hex.
Here, we use this macro to cast this memory address to a pointer, which can point to a
struct of GPIO Type.
As result, we can modify the output data register (ODR) in this way.
By using a struct, software can access all registers of a peripheral more easily.
Please visit the book website for tutorials, and project templates.