Discussing C Programming – Address Manipulation and Byte Control in C Language

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

Discussing C Programming - Address Manipulation and Byte Control in C Language

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

  1. Clarify Pointer Types: Always use the correct type of pointer to access data
  2. Boundary Checks: Check pointer range before operations to avoid overflow
  3. Use of volatile Modifier: Use volatile for hardware access to ensure reading from memory each time
  4. 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.

Leave a Comment