C Language – Understanding Pointers
1. C Language Pointers
- A pointer in C is a variable that stores a memory address.
- It stores the "location" (address) where data resides.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
const char *pstr = "Hello World!";
char *ptr = NULL;
const size_t len = strlen(pstr);
ptr = malloc(len + 1);
memset(ptr, 0x00, len + 1);
memcpy(ptr, pstr, len);
printf("%p[%p] → %s\n", &ptr, ptr, ptr);
free(ptr);
return 0;
}- The
ptrpointer variable resides in the Stack region. - Using
malloc, a memory space is allocated in the Heap region, and its address is stored in the Stack regionptrvariable. - Via
memcpy, the "Hello World!" string data is copied into the allocated space in the Heap region.
0x7ffffc12fd50[0x5a14a05396b0] → Hello World!&ptris the address of the pointer variable itself, and the string data is stored at the address held in theptrvariable.
A pointer does not store data directly; it stores the memory address where data is held.
1.1. Stack vs. Heap
1.1.1. Stack
- This is a memory region that stores local variables, parameters, and return addresses created when a function is called.
- LIFO (Last-In First-Out) structure.
- The last data in is the first data out.
- Memory is created when a function starts and automatically released when it ends.
- The size is determined at compile time and uses contiguous memory.
- Excessive use can cause a Stack Overflow.
- Windows allocates 1 MB and Linux allocates 8 MB of stack per thread by default.
1.1.2. Heap
- This is a memory region allocated dynamically while the program is running.
- The developer must allocate and free it manually.
- malloc(), calloc(), realloc(), free() functions are used
- A system call occurs to allocate memory.
- The size can be determined at runtime.
1.2. Execution Process
1.2.1. malloc
- Creates internal memory management information (Header) and allocates memory.
ptr = malloc(len + 1); // len=12, malloc(13)Allocates: 16 (header) + 13 (data) = 29 bytes
[Heap after malloc]
0x5a14a05396a0 (allocation start)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ HEADER: size=13, flags=P(1:allocated) │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
0x5a14a05396b0 (malloc return value)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│??│??│??│??│??│??│??│??│??│??│??│??│??│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘1.2.2. memset
- Initializes the allocated memory.
memset(ptr, 0x00, len + 1);Initialize a total of 13 bytes with zero
[Heap after memset]
0x5a14a05396b0
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│00│00│00│00│00│00│00│00│00│00│00│00│00│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘1.2.3. memcpy
- Copies data for the specified length.
memcpy(ptr, pstr, len);Copy 12 bytes from
pstr
[Heap after memcpy]
0x5a14a05396b0
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│H │e │l │l │o │ │W │o │r │l │d │! │00│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘1.2.4. free
- Changes the state of the internal memory management information (Header) to make the memory reusable.
free(ptr);Free the allocated memory
[Heap after free]
0x5a14a05396a0
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ HEADER: size=13, flags=P(0:deallocated) │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
0x5a14a05396b0
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│H │e │l │l │o │ │W │o │r │l │d │! │00│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘1.3. Dynamic Memory Allocation
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void exam1(const char *pstr)
{
const size_t len = strlen(pstr);
char *p = malloc(len + 1);
memset(p, 0x00, len + 1);
strncpy(p, pstr, len);
printf("%s\n", p);
free(p);
}
void exam2(const char *pstr)
{
const size_t len = strlen(pstr);
char *p = calloc(len + 1, sizeof(char));
strncpy(p, pstr, len);
printf("%s\n", p);
free(p);
}
void exam3(const char *pstr)
{
const size_t len = strlen(pstr);
const size_t unit = 4;
size_t block = unit;
char *p = malloc(block);
size_t n = 0;
for (size_t i = 0; i < len; i++) {
if (n + 1 >= block) {
block += unit;
p = realloc(p, block);
}
p[n++] = pstr[i];
}
p[n] = '\0';
printf("%s\n", p);
free(p);
}
int main()
{
char *pstr = "01234567890123456789";
exam1(pstr);
exam2(pstr);
exam3(pstr);
return 0;
}- Input string for
main:"01234567890123456789"(length 20, buffer of 21 needed including NULL)
exam1:malloc+memset+strncpyexam2:calloc+strncpyexam3: repeatedreallocexpansion while appending characters after initialmalloc
1.3.1. malloc
- Allocates memory for the requested number of bytes.
- May contain uninitialized memory (
garbage value).
[Heap after malloc]
0x5a14a05396a0 (allocation start)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ HEADER: size=21, flags=P(1:allocated) │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
0x5a14a05396b0 (malloc return value)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│??│??│??│??│??│??│??│??│??│??│??│??│??│??│??│??│??│??│??│??│??│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘1.3.2. calloc
- Allocates memory for the requested number of bytes and initializes the entire region to
0.
[Heap after calloc]
0x5a14a05397a0 (allocation start)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ HEADER: size=21, flags=P(1:allocated) │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
0x5a14a05397b0 (calloc return value)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│00│00│00│00│00│00│00│00│00│00│00│00│00│00│00│00│00│00│00│00│00│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘1.3.3. realloc
- Changes the size of already allocated memory.
- Suitable for dynamic buffer expansion when the final size is not known in advance.
- May retain the same address or move to a new one.
reallocrequires the memory space to be contiguous.- It can extend behind the existing space, but if there is insufficient space, it may be moved to a new location.
[Step A: initial malloc(4)]
0x5a14a05398a0 (allocation start)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ HEADER: size=4, flags=P(1:allocated) │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
0x5a14a05398b0 (malloc return value)
┌──┬──┬──┬──┐
│??│??│??│??│
└──┴──┴──┴──┘
[Step B: realloc to 8]
0x5a14a05399a0 (new allocation start)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ HEADER: size=8, flags=P(1:allocated) │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
0x5a14a05399b0 (realloc return value)
┌──┬──┬──┬──┬──┬──┬──┬──┐
│..│..│..│..│??│??│??│??│
└──┴──┴──┴──┴──┴──┴──┴──┘
...
[Step F: realloc to 24 (final capacity)]
0x5a14a0539da0 (new allocation start)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│ HEADER: size=24, flags=P(1:allocated) │
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
0x5a14a0539db0 (realloc return value)
┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐
│..│..│..│..│..│..│..│..│..│..│..│..│..│..│..│..│..│..│..│..│??│??│??│??│
└──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘1.4. strcpy vs. memcpy
strcpyis a function that copies a string until it encounters a null character ('\0').memcpyis a function that copies memory byte-by-byte for the specified size.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
char str[] = {'H', 'e', 'l', 'l', 'o', '\0', 'W', 'o', 'r', 'l', 'd', '!', '\0'};
printf("str=%s\n", str);
char *ptr1 = NULL;
ptr1 = malloc(sizeof(str));
memset(ptr1, 0x00, sizeof(str));
strcpy(ptr1, str);
printf("ptr1=%s\n", ptr1+6);
free(ptr1);
char *ptr2 = NULL;
ptr2 = malloc(sizeof(str));
memset(ptr2, 0x00, sizeof(str));
memcpy(ptr2, str, sizeof(str));
printf("ptr2=%s\n", ptr2+6);
free(ptr2);
return 0;
}str=Hello
ptr1=
ptr2=World!printf("str=%s\n", str);- %s stops printing at the first '\0', so only "Hello" is printed.
strcpy(ptr1, str);- strcpy copies the string up to the first '\0', so only "Hello\0" is stored in ptr1.
- Therefore,
ptr1 + 6points just past '\0' and prints an empty string.
memcpy(ptr2, str, sizeof(str));- Since memcpy copies memory for the specified size, the entire "Hello\0World!\0" is copied.
- Therefore,
ptr2 + 6prints "World!".
※ In the code examples used for illustration, NULL checks on the return values of malloc, calloc, and realloc have been omitted for simplicity.
In a real development environment, however, you should always account for the possibility of allocation failure, and defensive code that checks whether the return value is NULL is essential.