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 ptr pointer 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 region ptr variable.
  • Via memcpy, the "Hello World!" string data is copied into the allocated space in the Heap region.
0x7ffffc12fd50[0x5a14a05396b0] → Hello World!
  • &ptr is the address of the pointer variable itself, and the string data is stored at the address held in the ptr variable.

A pointer stores the memory address where data is held.

A pointer does not store data directly; it stores the memory address where data is held.

1.1. Stack vs. Heap

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 + strncpy
  • exam2: calloc + strncpy
  • exam3: repeated realloc expansion while appending characters after initial malloc

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.
    • realloc requires 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

  • strcpy is a function that copies a string until it encounters a null character ('\0').
  • memcpy is 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 + 6 points 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 + 6 prints "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.