Understanding Pointers in C Programming

  1. Memory Address

After we write a program source file and compile it, the resulting binary executable file is stored on the computer’s hard drive. At this point, it is a static file, generally referred to as a program. When this program is started, the operating system will perform the following actions:

1. Copy the program’s contents (code segment, data segment) from the hard drive to memory;
2. Create a data structure PCB (Process Control Block) to describe various information about this program (e.g., resources used, open file descriptors…);
3. Locate the entry function’s address in the code segment, allowing the CPU to start executing from this address.

Understanding Pointers in C Programming

When the program begins execution, it becomes a dynamic state, generally referred to as a process. In our program, we define variables and use them through variable names. A variable is a tangible entity, while the variable name is an abstract concept that represents this variable. So, after we define a variable, where is it stored? It resides in the data area of memory. Memory is a large storage area divided by the operating system into smaller spaces, and the operating system manages memory through addresses.

Understanding Pointers in C Programming

The smallest storage unit in memory is a byte (8 bits), and a complete memory space is made up of these continuous bytes. In the above diagram, each small box represents one byte. However, it seems that this is not how memory models are often depicted in books; a more common representation is as follows:

Understanding Pointers in C Programming

This illustrates four continuous bytes together, which makes it easier to express and understand, especially when delving into code alignment-related knowledge.

2. Variables

In C programming, we use variables to “represent” data and function names to “represent” functions. Variable names and function names are mnemonics used by programmers. Both variables and functions ultimately need to be placed in memory to be used by the CPU, and all information (code and data) in memory is stored in binary form; the computer does not distinguish between code and data in terms of format. The CPU requires addresses to access memory, not variable names or function names.

The question arises: how is the mapping (association) between variable names used in program code and the addresses where variables are stored in memory achieved?

The answer is: the compiler! When compiling the textual format of a C program file, the compiler arranges various addresses in the program based on the target runtime platform (where the compiled binary program will run: on an x86 platform computer or an ARM platform development board?). For example, it assigns addresses for loading into memory, the entry address of the code segment, etc. At the same time, the compiler converts all variable names in the program into their corresponding storage addresses in memory.

Variables have two important attributes: the variable type and the variable value.
Example: A variable defined in code
int a = 20;
The type is int, and the value is 20. The storage model of this variable in memory is as follows:

Understanding Pointers in C Programming

When we use the variable name a in code, it corresponds to the data stored in the memory storage unit at address 0x11223344 during program execution. Thus, we can understand that the variable name a is equivalent to the address 0x11223344. In other words, if we knew in advance that the compiler assigned variable a to the storage unit at address 0x11223344, we could directly use this address value to operate on the variable in the program.

In the diagram above, the value of variable a is 20, occupying 4 storage units in memory, which is 4 bytes. Why is it 4 bytes? The C standard does not specify how many bytes each data type must occupy; this depends on the specific machine and compiler.

For example, in a 32-bit compiler:
char: 1 byte;
short int: 2 bytes;
int: 4 bytes;
long: 4 bytes.
For example, in a 64-bit compiler:
char: 1 byte;
short int: 2 bytes;
int: 4 bytes;
long: 8 bytes.

For convenience, the following examples will use 32-bit, meaning that an int variable occupies 4 bytes in memory.

Additionally, the continuous addresses 0x11223344, 0x11223345, 0x11223346, and 0x11223347 are used to store the value 20 of variable a. In the illustration, hexadecimal is used to represent these addresses, and the decimal value 20 converted to hex is: 0x00000014, so the four bytes are stored as 0x00, 0x00, 0x00, 0x14 sequentially (the storage order involves endianness but does not affect text comprehension).

According to this illustration, if we want to know where variable a is stored in memory, we can use the address operator & to do so, as follows:
printf(“&a = 0x%x \n”, &a);
This statement will print: &a = 0x11223344.
Consider this: in a 32-bit system, how many bytes does a pointer variable occupy? (4 bytes)
3. Pointer Variables

Pointer variables can be understood on two levels:

Firstly, a pointer variable is a variable, so it has all the attributes of a variable: type and value. Its type is a pointer, and its value is the address of another variable. Since it is a variable, memory must be allocated for it. This memory space stores the address of other variables.

The data type that the pointer variable points to is determined when the pointer variable is defined. For example: int *p; means that the pointer points to an int type data.

To answer the earlier question, in a 32-bit system, a pointer variable occupies 4 bytes of memory space. This is because the CPU uses a 32-bit address space (4 bytes) to address memory, meaning that 4 bytes can store the address of a memory unit. The value stored in the pointer variable is the address, hence it requires 4 bytes of space to store a pointer variable’s value.

Example:
int a = 20;
int *pa;
pa = &a
printf(“value = %d \n”, *pa);
The storage model in memory is as follows:

Understanding Pointers in C Programming

For pointer variable pa, firstly it is a variable, thus it requires a space in memory to store this variable, which has the address 0x11223348;

Secondly, this memory space contains the address of variable a, which is 0x11223344, so the address space of pointer variable pa stores the value 0x11223344.

Here are explanations for the two operators & and *:
&: The address operator, used to obtain the address of a variable. In the above code, &a is used to get the storage address of variable a, which is 0x11223344.
*: This operator is used in two scenarios: when defining a pointer and when obtaining the value of the variable pointed to by a pointer.

int pa; This statement indicates that the defined variable pa is a pointer; the preceding int indicates that the pointer pa points to an int type variable. However, at this moment, we have not assigned a value to pa, meaning that the 4 bytes in the storage unit corresponding to pa are uninitialized, which could be 0x00000000 or any other number, but it is uncertain;

In the printf statement, * indicates obtaining the value of the int type variable pointed to by pa, which is known as dereferencing. We just need to remember that it is about obtaining the value of the pointed variable.

4. Operations on Pointer Variables
Operations on pointer variables include three aspects:
1. Manipulating the value of the pointer variable itself;
2. Obtaining the data pointed to by the pointer variable;
3. How to use/interpret the content pointed to by the pointer variable in terms of data type.
4.1 Value of the Pointer Variable Itself

int a = 20; This statement defines variable a, and in subsequent code, simply writing a indicates that we want to operate on the value stored in variable a, with two operations: read and write.

printf(“a = %d \n”, a); This statement reads the value in variable a, which is 20; a = 100; This statement writes the value 100 into variable a.

Similarly, int *pa; This statement is used to define pointer variable pa, and in subsequent code, simply writing pa indicates that we want to operate on the value stored in pointer variable pa:

printf(“pa = %d \n”, pa); This statement reads the value in pointer variable pa, which is 0x11223344; pa = &a This statement writes a new value into pointer variable pa.To emphasize again, the pointer variable stores addresses; if we know in advance that variable a’s address is 0x11223344, we can also assign it like this: pa = 0x11223344;

Consider this: if we execute the statement printf(“&pa =0x%x \n”, &pa); what will the printed result be?

As mentioned earlier, the operator & is used to obtain addresses, so &pa indicates obtaining the address of pointer variable pa. In the above memory model, pointer variable pa is stored at address 0x11223348, so the printed result will be: &pa = 0x11223348.

4.2 Obtaining Data Pointed to by Pointer Variables

The data type pointed to by the pointer variable is determined at the time of definition, meaning that pointer pa points to an int type data. Therefore, when executing the statement printf(“value = %d \n”, *pa); we first recognize that pa is a pointer storing an address (0x11223344), and then we use the operator * to obtain the value stored in the memory space corresponding to this address (0x11223344); since we specified that pa points to an int type during its definition, we know that the value stored at address 0x11223344 is an int type data.

4.3 How to Use/Interpret the Content Pointed to by Pointer Variables
Consider the following code:
int a = 30000;
int *pa = &a
printf(“value = %d \n”, *pa);
Based on the above description, we know that the printf output will be value = 30000, and the decimal value 30000 converted to hexadecimal is 0x00007530, with the memory model as follows:

Understanding Pointers in C Programming

Now let’s conduct a test:
char *pc = 0x11223344;
printf(“value = %d \n”, *pc);

The pointer variable pc, when defined, indicates that it points to data of type char, and the address stored in pc is 0x11223344. When using *pc to obtain data, it will read the data at address 0x11223344 in char format, thus printing value = 0 (in computers, ASCII codes are stored using equivalent numbers).

This example illustrates an important concept: everything in memory is a number, and how we operate (interpret) the data in a memory address is entirely dictated by our code to the compiler. In the previous example, although the first four bytes of memory starting at address 0x11223344 store the value of int variable a, we instructed pointer pc to interpret the content at this address as char type data, which is entirely legal.

The above content encapsulates the fundamental principles of pointers. Once you understand these principles clearly, the rest is just a matter of gaining experience and practice.

Leave a Comment