1. Basics of Address Manipulation
1. Nature of Pointer Variables
A pointer is a variable that stores a memory address, and its core function is to enable direct access to any memory unit. Unlike ordinary variables, the value of a pointer is a memory address rather than data.
int num =100;
int* ptr =# // ptr stores the address of num (e.g., 0x20000000)
printf("num address: %p\n", &num); // Output variable address
printf("ptr value: %p\n", ptr); // Address stored in pointer variable
printf("value pointed by ptr: %d\n", *ptr); // Access data through dereferencing *
2. Representation and Range of Addresses
- 32-bit System: Address is a 32-bit integer (range 0x00000000~0xFFFFFFFF)
- 64-bit System: Address is a 64-bit integer (range 0x0000000000000000~0xFFFFFFFFFFFFFFFF)
- Address Format: Typically represented in hexadecimal (e.g., 0x20001234)
3. Basic Operations on Pointers

Pointer Offset Example
int arr[3]={1,2,3};
int* p = arr; // p points to arr[0] (address 0x20000000)
p++; // p points to arr[1] (address 0x20000004, moves 4 bytes)
*p =10; // arr[1] = 10
2. Byte Access Techniques
1. Key to Byte Access: char Pointer
<span>char *</span> is the only pointer type in C that can access a single byte (sizeof(char)=1)
// Access any byte through char pointer<span><span>uint32_t</span></span><span> data </span><span><span>=</span></span><span><span>0x12345678</span></span><span><span>;</span></span><span><span>char</span></span><span><span>*</span></span><span> p </span><span><span>=</span></span><span><span>(</span></span><span><span>char</span></span><span><span>*</span></span><span><span>)</span></span><span><span>&data;</span></span><span><span>// Access by byte (little-endian system)</span></span><span><span>printf</span></span><span><span>(</span></span><span><span>"Byte 0: 0x%02X\n"</span></span><span><span>,</span></span><span> p[0]</span><span><span>);</span></span><span><span>// 0x78 (low address byte)</span></span><span><span>printf</span></span><span><span>(</span></span><span><span>"Byte 1: 0x%02X\n"</span></span><span><span>,</span></span><span> p[1]</span><span><span>);</span></span><span><span>// 0x56</span></span><span><span>printf</span></span><span><span>(</span></span><span><span>"Byte 2: 0x%02X\n"</span></span><span><span>,</span></span><span> p[2]</span><span><span>);</span></span><span><span>// 0x34</span></span><span><span>printf</span></span><span><span>(</span></span><span><span>"Byte 3: 0x%02X\n"</span></span><span><span>,</span></span><span> p[3]</span><span><span>);</span></span><span><span>// 0x12 (high address byte)</span></span>
2. Endianness (Little-endian and Big-endian) Issues
- Little-endian Mode (x86/ARM): Low byte stored at low address (e.g., 0x12345678 → 0x78,0x56,0x34,0x12)
- Big-endian Mode (PowerPC): Low byte stored at high address (e.g., 0x12345678 → 0x12,0x34,0x56,0x78)
- Detection Method: Determine by accessing the first byte of a multi-byte variable through a char pointer
Endianness Detection Code
bool is_little_endian(){
uint32_t test =0x01234567;
return((char*)&test)[0]==0x67; // Returns true for little-endian
}
3. Byte Operation Example: Data Parsing
Parsing multi-byte data from a buffer (e.g., network protocol parsing)
uint8_t<span> buffer</span><span><span>[</span></span><span><span>]</span></span><span><span>=</span></span><span><span>{</span></span><span><span>0x01</span></span><span><span>,</span></span><span><span>0x02</span></span><span><span>,</span></span><span><span>0x03</span></span><span><span>,</span></span><span><span>0x04</span></span><span><span>,</span></span><span><span>0x05</span></span><span><span>,</span></span><span><span>0x06</span></span><span><span>,</span></span><span><span>0x07</span></span><span><span>,</span></span><span><span>0x08</span></span><span><span>}</span></span><span><span>;</span></span><span><span>uint32_t</span></span><span><span>*</span></span><span> p32 </span><span><span>=</span></span><span><span>(</span></span><span><span>uint32_t</span></span><span><span>*</span></span><span><span>)</span></span><span>buffer;</span><span><span>// Parsing result in little-endian system:</span></span><span><span>printf</span></span><span><span>(</span></span><span><span>"32-bit data: 0x%08X\n"</span></span><span><span>,</span></span><span> p32[0]</span><span><span>);</span></span><span><span>// 0x04030201 (buffer[0-3])</span></span><span><span>printf</span></span><span><span>(</span></span><span><span>"32-bit data: 0x%08X\n"</span></span><span><span>,</span></span><span> p32[1]</span><span><span>);</span></span><span><span>// 0x08070605 (buffer[4-7])</span></span>
3. Practical Application Scenarios
1. Peripheral Register Access (Embedded Development)
Directly access hardware register addresses through pointers
// Define GPIO register address (STM32 example)
#define GPIOA_BASE 0x40010800
#define GPIOA_ODR (*(volatile uint32_t*)(GPIOA_BASE + 0x0C))
// Operate register (control LED)
GPIOA_ODR |=(1<<5); // Set PA5 pin to 1 (turn on LED)
2. Memory Data Packing/Unpacking
// Pack multiple bytes into a 32-bit integer
uint8_t bytes[4]={0x11,0x22,0x33,0x44};
uint32_t data =(bytes[3]<<24)|(bytes[2]<<16)|(bytes[1]<<8)| bytes[0];
// data = 0x44332211 (big-endian packing)
3. Buffer Operations and Boundary Checks
// Safe byte copy (prevent overflow)
void safe_copy(uint8_t* dest, const uint8_t* src, size_t n){
for(size_t i =0; i < n; i++){
dest[i]= src[i]; // Byte-level copy
}
}
4. Common Issues and Risks
1. Pointer Out-of-Bounds Access
int arr[3]={1,2,3};
int* p = arr;
p[5]=10; // Out-of-bounds access (unallocated memory), may cause program crash
2. Dangling Pointers and Wild Pointers
- Wild Pointer: Uninitialized pointer (points to a random address)
- Dangling Pointer: Pointer that points to freed memory
- Defensive Measures: Initialize pointers before use, set to NULL after freeing
3. Type Conversion Risks
Converting between different types of pointers may lead to address alignment issues
uint32_t data;
uint16_t* p =(uint16_t*)&data; // May be unaligned
*p =0x1234; // Alignment error (some architectures do not support unaligned access)
5. Safety Operation Guidelines
- Clarify Pointer Types: Always use the correct type of pointer to access data
- Boundary Checks: Check pointer range before operations to avoid overflow
- Use of volatile Modifier: Use volatile for hardware access to ensure reading from memory each time
- Aligned Access: Ensure address alignment for multi-byte data access (e.g., int needs 4-byte alignment)
// Safe unaligned access example (using char pointer)
uint32_t read_unaligned(const void* ptr){
const char* p =(const char*)ptr;
return(p[3]<<24)|(p[2]<<16)|(p[1]<<8)| p[0];
}
6. Conclusion
Address manipulation and byte control are core advantages of the C language, achieved through pointers
- Direct memory access (hardware control, efficient data processing)
- Byte-level precision operations (network protocols, file format parsing)
- Peripheral control in embedded systems (register operations)
Mastering these techniques requires understanding memory models, address representation, and type conversion rules, while also focusing on safety and portability.