Advanced C Language: Data Types, Pointers, and Memory Management

1.1 Analysis of Data Types

1.1.1 Array Parameters Decay to Pointers

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

// When an array is passed as a function parameter, it decays to a pointer.
// The size inside the brackets for the array parameter is ignored, but cannot be negative or zero.
// void print_array(int a[1], int n)
// void print_array(int a[], int n)
void print_array(int *a, int n) {
    int i = 0;
    printf("Array elements: ");
    for (i = 0; i < n; i++) {
        printf("%d ", a[i]);
    }
    printf("\n");

    // 'a' is treated as a pointer. On a 32-bit compiler, pointers occupy 4 bytes.
    int m = sizeof(a) / sizeof(a[0]); // number of elements
    printf("After decay, m = %d\n", m);
}

int main(void) {
    int arr[] = {10, 7, 1, 9, 4, 6, 7, 3, 2, 0};
    int n = 10;
    int m = sizeof(arr) / sizeof(arr[0]);
    printf("Original array element count m = %d\n", m);
    print_array(arr, n);
    system("pause");
    return 0;
}

array decay

1.1.2 Essence of Data Types

  1. A data type can be seen as a mold for creating variables: it is an alias for a fixed memory size.
  2. Purpose of data types: the compiler estimates the memory space to allocate for an object (variable).
  3. Note: data types are just molds; the compiler does not allocate space until a variable (entity) is created based on the type.
#include <stdio.h>

int main(void) {
    int a = 10;      // instructs compiler to allocate 4 bytes
    int b[10];       // instructs compiler to allocate 4 * 10 = 40 bytes

    printf(" b: %d\n b+1: %d\n &b: %d\n &b+1: %d\n", b, b + 1, &b, &b + 1);

    // b+1 and &b+1 differ because 'b' and '&b' have different data types.
    // 'b' represents the address of the first element.
    // '&b' represents the address of the entire array, which has the same value as the first element's address.

    return 0;
}

data type size

1.1.3 Size of Data Types

#include <stdio.h>

int main(void) {
    int a = 10;      // allocate 4 bytes
    int b[10];       // allocate 40 bytes

    printf("sizeof(a): %d\n", sizeof(a));
    printf("sizeof(int *): %d\n", sizeof(int *));
    printf("sizeof(b): %d\n", sizeof(b));
    printf("sizeof(b[0]): %d\n", sizeof(b[0]));
    printf("sizeof(*b): %d\n", sizeof(*b));
    printf("*b: %d\n", *b);
    return 0;
}

sizeof

1.1.4 Typedef for Data Types

#include <stdio.h>

struct Person {
    char name[64];
    int age;
};

typedef struct Person {
    char name[64];
    int age;
} person_t;

typedef unsigned int u32;   // alias for unsigned int

int main(void) {
    struct Person p1;
    person_t p2;
    u32 a;

    p1.age = 10;
    p2.age = 11;
    a = 10;

    return 0;
}

1.2 Essence of Variables

  1. Programs use variables to request and name memory space: int a = 0;
  2. Access memory space through variable names.
  3. Methods to modify a variable:
    • Directly
    • Indirectly
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int i = 0;

    // Directly modify memory via variable
    i = 10;

    int *p = &i;
    printf("&i: %d\n", &i);
    printf("p: %d\n", p);

    // Indirectly modify memory via pointer
    *p = 100;
    printf("i = %d, *p = %d\n", i, *p);

    system("pause");
    return 0;
}

1.3 Memory Four-Area Model

memory model

1.3.1 Global Area

#include <stdio.h>

char *getStr1() {
    char *p1 = "abcdefg2"; // string literal in constant area
    return p1;
}

char *getStr2() {
    char *p2 = "abcdefg2"; // same literal, same address
    return p2;
}

int main(void) {
    char *p1 = NULL;
    char *p2 = NULL;
    p1 = getStr1();
    p2 = getStr2();

    printf("p1: %s, p2: %s\n", p1, p2);
    printf("p1: %p, p2: %p\n", p1, p2);

    return 0;
}

Since both strings are identical, the compiler allocates only one memory location.

global area addresses

1.3.2 Stack Area

Stack space is allocated and reclaimed by the system.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char *get_str() {
    char str[] = "abdefhgj"; // stack area
    return str;
}

int main(void) {
    char buf[128] = {0};
    strcpy(buf, get_str());
    printf("buf = %s\n", buf); // garbage or unpredictable
    printf("\n");
    system("pause");
    return 0;
}

stack

In this compiler, the string is copied before the stack memory is freed, so buf gets a determinate value. Different compilers may behave differently.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char *get_str() {
    char str[] = "abdefhgj"; // stack area
    printf("str = %s\n", str);
    return str;
}

int main(void) {
    char buf[128] = {0};
    char *p = NULL;
    p = get_str();
    printf("p = %s\n", p); // junk

    printf("\n");
    system("pause");
    return 0;
}

stack2 stack3

Here, p points to the now-invalid stack memory, leading to undefined behavior.

1.3.3 Heap Area

Heap space is allocated and freed by the programmer.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

char *get_str2() {
    // Allocate 100 bytes on the heap, return its address.
    // Cast to char* and assign to a char* pointer.
    // If allocation fails, malloc returns NULL.
    char *tmp = (char *)malloc(100);
    if (tmp == NULL) {
        return NULL;
    }
    strcpy(tmp, "absfsgjghgu");
    return tmp;
}

int main(void) {
    char *p = NULL;
    p = get_str2();
    if (p != NULL) {
        printf("p = %s\n", p);
        free(p);
        p = NULL;
    }

    system("pause");
    return 0;
}

heap

1.4 Function Call Model

call model 1 call model 2 call model 3 call model 4 call model 5

2. Pointers

2.1 Strengthening Pointers

2.1.1 Pointer as a Data Type

  1. A pointer variable occupies memory and stores an address. Test its size.
  2. *p operates on memory:
    • In declaratino, * indicates a pointer.
    • In usage, * dereferences the pointer to access the value at the pointed memory. *p on the left side of = writes to memory; on the right side, it reads from memory.
  3. The pointer variable and the memory it points to are separate concepts.
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    char *p = NULL;
    char buf[] = "abcdef";
    printf("p1 = %d\n", p);
    p = buf;
    printf("p2 = %d\n", p);
    p = p + 1; // change pointer value, i.e., change where it points
    printf("p3 = %d\n", p);
    printf("buf = %s\n", buf);
    printf("*p = %c\n", *p); // single character
    printf("*p = %s\n", p);  // string from new position

    printf("Changing the content pointed to does not affect the pointer's value\n");
    buf[1] = '2';
    printf("p3 = %d\n", p);
    printf("buf2 = %s\n", buf);

    *p = 'm';
    printf("p4 = %d\n", p);
    printf("buf3 = %s\n", buf);

    system("pause");
    return 0;
}

pointer example

Note: When writing to memory, ensure it is writable.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    char *str_literal = "abcsdefh"; // string literal, read-only
    // str_literal[2] = '3'; // error: writing to read-only memory

    char arr[] = "sbgdjdgj";
    arr[1] = '3'; // OK
    printf("arr = %s\n", arr);

    system("pause");
    return 0;
}
  1. A pointer's type determines the step size when incrementing (p++ is equivalent to (unsigned char*)p + sizeof(*p)).
  2. Assigning to a pointer changes its value (where it points), unrelated to the pointed memory.
  3. Do not copy memory to NULL or unknown illegal addresses.
char buf[100] = "absfdghh";
int i;
for (i = 0; i < strlen(buf); i++) {
    p = &buf[i]; // or p = buf + i;
    printf("p = %d, %c\n", p, *p);
}

2.1.2 Modifying Arguments via Pointers

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int get_a() {
    int a = 10;
    return a;
}

void get_a2(int a) {
    a = 22; // local copy, does not affect original
}

void get_a3(int *p) {
    *p = 33; // modify through pointer
}

void get_a4(int *a1, int *a2, int *a3, int *a4) {
    *a1 = 1;
    *a2 = 2;
    *a3 = 3;
    *a4 = 4;
}

int main(void) {
    int a = get_a();
    printf("a = %d\n", a);
    get_a2(a);
    printf("a2 = %d\n", a); // unchanged
    get_a3(&a);
    printf("a3 = %d\n", a); // changed

    int a1, a2, a3, a4;
    get_a4(&a1, &a2, &a3, &a4);
    printf("a1=%d, a2=%d, a3=%d, a4=%d\n", a1, a2, a3, a4);

    system("pause");
    return 0;
}

pointer as parameter

2.1.3 Indirect Assignment via Double Pointers

int *p = 0x1122;
int **q = &p; // q is a pointer to pointer p
// *q gets the value of p (the address it holds)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void fun(int *p) {
    p = (int*)0xaabb;
    printf("fun: p = %p\n", p);
}

void fun2(int **p) {
    *p = (int*)0xeeff;
    printf("fun2: *p = %p\n", *p);
}

int main(void) {
    int *p = (int*)0x1122;
    printf("p = %p\n", p);
    fun(p); // pass by value: p unchanged
    printf("fun: p1 = %p\n", p);
    fun2(&p); // pass address: p changed
    printf("fun2: p2 = %p\n", p);

    system("pause");
    return 0;
}

double pointer

How to declare a pointer of appropriate type: To store the address of a variable, add a * to its type.

int b;
int *q = &b;   // q can store address of b
int **t = &q;  // t can store address of q

2.1.4 Understanding Double Pointers

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    int a = 10;
    int *p = &a;

    printf("hex p = %p\n", p);
    printf("hex &a = %p\n", &a);
    printf("dec p = %d\n", p);
    printf("dec &a = %d\n", &a);
    printf("&p = %d\n", &p);
    printf("*p = %d\n", *p);
    printf("a = %d\n", a);

    int *q = (int*)0x1122;
    printf("dec q = %d\n", q);
    printf("&q = %d\n", &q);
    // printf("*q = %d\n", *q); // undefined

    int **q1 = &p;
    printf("q1 = %d\n", q1);   // value of q1 is address of p
    printf("*q1 = %d\n", *q1); // value at q1 is value of p (address of a)

    system("pause");
    return 0;
}

double pointer understanding

2.1.5 Pointer Parameters: Input and Output Characteristics

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void fun(char *p /* in */) {
    strcpy(p, "abcdefg");
}

void fun2(char *p) {
    if (p == NULL) return;
    strcpy(p, "abcdefg");
}

void fun3(char **p /* out */, int *len) {
    if (p == NULL) return;
    char *tmp = (char*)malloc(100);
    if (tmp == NULL) return;
    strcpy(tmp, "abcdefg");
    *p = tmp;
    *len = strlen(tmp);
}

int main(void) {
    // Input: caller allocates memory
    char buf[100] = {0};
    fun(buf);
    printf("buf = %s\n", buf);

    char *str = NULL;
    fun2(str); // dangerous: copying to NULL or unknown address
    printf("str = %d\n", str);

    // Output: callee allocates memory, pass by address
    char *p = NULL;
    int len = 0;
    fun3(&p, &len);
    if (p != NULL) {
        printf("p = %s, len = %d\n", p, len);
    }

    system("pause");
    return 0;
}

input output input output2

3. Strings

3.1 Basic String Operations

3.1.1 String Initialization

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    char buf[] = {'a', 'b', 'c'}; // no null terminator, prints garbage
    printf("buf = %s\n", buf);

    char buf1[100] = {'a', 'b', 'c'}; // rest filled with 0
    printf("buf1 = %s\n", buf1);

    char buf2[100] = {0};
    printf("buf2 = %s\n", buf2);

    char buf3[2] = {'1', '2'}; // overflow
    printf("buf3 = %s\n", buf3);

    char buf4[50] = {'1', 'a', 'b', '0', '7'};
    printf("buf4 = %s\n", buf4);

    char buf5[50] = {'1', 'a', 'b', 0, '7'};
    printf("buf5 = %s\n", buf5);

    char buf6[50] = {'1', 'a', 'b', '\0', '7'};
    printf("buf6 = %s\n", buf6);

    char buf7[] = "asbfhjh"; // commonly used
    printf("strlen = %d, sizeof = %d\n", strlen(buf7), sizeof(buf7));

    char buf8[100] = "asbfhjh";
    printf("strlen = %d, sizeof = %d\n", strlen(buf8), sizeof(buf8));

    system("pause");
    return 0;
}

string init

3.1.2 Accessing Strings with Pointers and Arrays

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    char buf[] = "gasdfgjkp";
    int i = 0;
    int n = strlen(buf);

    // Using array subscript
    for (i = 0; i < n; i++) {
        printf("%c", buf[i]);
    }
    printf("\n");

    // Using pointer
    char *p = buf;
    for (i = 0; i < n; i++) {
        printf("%c", p[i]);
    }
    printf("\n");

    // p[i] is equivalent to *(p+i)
    for (i = 0; i < n; i++) {
        printf("%c", *(p + i));
    }
    printf("\n");

    for (i = 0; i < n; i++) {
        printf("%c", *(buf + i));
    }

    // buf and p are not entirely equivalent: p++ is legal, buf++ is not (buf is constant)
    printf("\n");
    system("pause");
    return 0;
}

3.1.3 String Copy

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void my_strcpy(char *dst, char *str) {
    int i;
    for (i = 0; str[i] != '\0'; i++) {
        dst[i] = str[i]; // equivalent to *(dst+i) = *(str+i)
    }
    dst[i] = 0; // null-terminate
}

void my_strcpy2(char *dst, char *str) {
    while (*str != 0) {
        *dst = *str;
        dst++;
        str++;
    }
    *dst = 0;
}

void my_strcpy3(char *dst, char *str) {
    while (*str != 0) {
        *dst++ = *str++; // post-increment: *dst = *str; then increment
    }
    *dst = 0;
}

void my_strcpy4(char *dst, char *str) {
    while ((*dst++ = *str++) != 0) {
        // copy and check
    }
}

void my_strcpy5(char *dst, char *str) {
    while (*dst++ = *str++) {
        // assignment is the condition; ends when null character copied
    }
}

int main(void) {
    char src[] = "asdfghjkl";
    char dest[100];

    my_strcpy5(dest, src);
    printf("%s\n", dest);

    system("pause");
    return 0;
}

Improved version with safety checks:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int my_strcpy6(char *dst, const char *src) {
    // Returns 0 on success, non-zero on failure
    if (dst == NULL || src == NULL) {
        return -1;
    }
    char *to = dst;
    const char *from = src;
    while (*to++ = *from++) {
        // copy until null
    }
    printf("my_strcpy6: dst = %s\n", dst);
    return 0;
}

int main(void) {
    char src[] = "asdfghjkl";
    char dest[100] = {0};
    int ret = my_strcpy6(dest, src);
    printf("%s\n", dest);
    if (ret != 0) {
        printf("my_strcpy6 err: %d\n", ret);
        return ret;
    }
    system("pause");
    return 0;
}

3.1.4 strstr with while and do-while Models

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int count_substr_do_while(const char *str) {
    int count = 0;
    const char *p = str;
    do {
        p = strstr(p, "abcd");
        if (p != NULL) {
            count++;
            p += strlen("abcd");
        } else {
            break;
        }
    } while (*p != '\0');
    return count;
}

int count_substr_while(const char *str) {
    int count = 0;
    const char *p = str;
    while ((p = strstr(p, "abcd")) != NULL) {
        p += strlen("abcd");
        count++;
        if (*p == '\0') break;
    }
    return count;
}

int my_count_strstr(const char *p, int *n) {
    const char *tmp = p;
    int i = 0;
    while ((tmp = strstr(tmp, "abcd")) != NULL) {
        tmp += strlen("abcd");
        i++;
        if (*tmp == 0) break;
    }
    *n = i;
    return 0;
}

int main(void) {
    const char *str = "11abcd456abcd7533abcd44abcd56";
    int n = 0;
    int ret = my_count_strstr(str, &n);
    if (ret == 0) {
        printf("%d\n", n);
    }
    system("pause");
    return 0;
}

3.1.5 Two-End Trimming Model

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

int trim_space(const char *input, char *output) {
    if (input == NULL || output == NULL) return -1;

    int begin = 0;
    int end = strlen(input) - 1;

    while (isspace(input[begin]) && input[begin] != 0) {
        begin++;
    }
    while (isspace(input[end]) && end > 0) {
        end--;
    }
    if (end < begin) return -2; // all whitespace

    int n = end - begin + 1;
    strncpy(output, input + begin, n);
    output[n] = '\0';
    return 0;
}

int main(void) {
    const char *p = "   abcdefg   ";
    int n;
    // Just compute length of trimmed string
    // encapsulated in function

    char buf[100] = {0};
    int ret = trim_space(p, buf);
    if (ret == 0) {
        printf("Trimmed: '%s'\n", buf);
    }

    system("pause");
    return 0;
}

Extract even/odd characters:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

int extract_even_odd(const char *src, char *even, char *odd) {
    if (src == NULL || even == NULL || odd == NULL) return -1;
    int len = strlen(src);
    for (int i = 0; i < len; i++) {
        if (i % 2 == 0) {
            *even++ = src[i];
        } else {
            *odd++ = src[i];
        }
    }
    *even = '\0';
    *odd = '\0';
    return 0;
}

int main(void) {
    const char *str = "a1b2c3d4";
    char buf1[50] = {0};
    char buf2[50] = {0};
    int ret = extract_even_odd(str, buf1, buf2);
    if (ret == 0) {
        printf("Even: %s\n", buf1);
        printf("Odd: %s\n", buf2);
    }
    system("pause");
    return 0;
}

Extract key-value pairs and trim spaces:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

int trim_space(const char *in, char *out) {
    if (in == NULL || out == NULL) return -1;
    int begin = 0;
    int end = strlen(in) - 1;
    while (isspace(in[begin]) && in[begin] != 0) begin++;
    while (isspace(in[end]) && end > 0) end--;
    if (end < begin) return -2;
    int n = end - begin + 1;
    strncpy(out, in + begin, n);
    out[n] = '\0';
    return 0;
}

int get_key_value(const char *key_value_str, const char *key, char *value, int *value_len) {
    if (key_value_str == NULL || key == NULL || value == NULL || value_len == NULL) return -1;

    const char *p = strstr(key_value_str, key);
    if (p == NULL) return -2;

    p += strlen(key);
    p = strstr(p, "=");
    if (p == NULL) return -3;

    p += strlen("=");

    char trimmed[256];
    int ret = trim_space(p, trimmed);
    if (ret != 0) return ret;

    strcpy(value, trimmed);
    *value_len = strlen(value);
    return 0;
}

int main(void) {
    const char *line = "key4=    value4";
    const char *key = "key4";
    char value[100] = {0};
    int len = 0;

    int ret = get_key_value(line, key, value, &len);
    if (ret == 0) {
        printf("Value: '%s', length: %d\n", value, len);
    } else {
        printf("Error: %d\n", ret);
    }

    system("pause");
    return 0;
}

key value

3.1.6 const Usage

// Look from left to right, skip the type, see which character is modified.
// If it's '*', the pointed memory cannot be changed.
// If it's the pointer variable, the pointer's value (where it points) cannot be changed.
const char *p = buf;  // equivalently: char const *p1 = buf;
char * const p2 = buf; // equivalently: char * const p2 = buf;
const char * const p3 = buf; // both read-only
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

typedef struct {
    int a;
    int b;
} MyStruct;

void fun(MyStruct *p) {
    // pointer can change; pointed content can change
    // p = NULL; // OK
    // p->a = 10; // OK
}

void fun1(const MyStruct *p) {
    // p = NULL; // OK
    // p->a = 10; // error
}

void fun2(MyStruct * const p) {
    // p = NULL; // error
    // p->a = 10; // OK
}

void fun3(const MyStruct * const p) {
    // read-only
    // MyStruct tmp;
    // tmp.a = p->a; // only reading
}

int main(void) {
    system("pause");
    return 0;
}

3.1.7 Pointers as Function Parameters (Pointer as Output)

Pass by value:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

int get_mem(char *p) {
    p = (char*)malloc(100);
    if (p == NULL) return -1;
    strcpy(p, "ajlgjdslga");
    printf("%s\n", p);
    return 0;
}

int main(void) {
    char *p = NULL;
    int ret = get_mem(p);
    if (ret != 0) {
        printf("get_mem err: %d\n", ret);
        return ret;
    }
    printf("p = %s\n", p); // still NULL, memory leak
    system("pause");
    return 0;
}

pass by value

Pass by address (double pointer as output):

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>

int get_mem2(char **p, int size) {
    if (p == NULL) return -1;
    char *tmp = (char*)malloc(size);
    if (tmp == NULL) return -1;
    strcpy(tmp, "ajlgjdslga");
    *p = tmp;
    return 0;
}

int main(void) {
    char *p = NULL;
    int ret = get_mem2(&p, 100);
    if (ret == 0) {
        printf("p = %s\n", p);
        free(p);
        p = NULL;
    }
    system("pause");
    return 0;
}

pass by address

4. Double Pointers

4.1 Double Pointers as Input

For an array of pointers *p[], the formal parameter should be **p.

Input: caller allocates memory. Output: callee allocates memory.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void sort_strings(char **arr, int n) {
    char *tmp;
    for (int i = 0; i < n - 1; i++) {
        for (int j = i + 1; j < n; j++) {
            if (strcmp(arr[i], arr[j]) > 0) {
                tmp = arr[i];
                arr[i] = arr[j];
                arr[j] = tmp;
            }
        }
    }
}

void print_strings(char **arr, int n) {
    for (int i = 0; i < n; i++) {
        printf("%s, ", arr[i]);
    }
    printf("\n");
}

int main(void) {
    char *strings[] = {"111111111", "00000000", "bbbbbbbbb", "aaaaaaaaa"};
    int n = sizeof(strings) / sizeof(strings[0]);

    printf("Before sorting:\n");
    print_strings(strings, n);
    sort_strings(strings, n);
    printf("After sorting:\n");
    print_strings(strings, n);

    system("pause");
    return 0;
}

4.2 Two-Dimensional Arrays

4.2.1 Size of Two-Dimensional Arrays

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    char arr[4][30] = {"22222", "11111", "aaaaa", "bbbbb"};
    printf("sizeof(arr) = %d\n", sizeof(arr));
    printf("sizeof(arr[0]) = %d\n", sizeof(arr[0]));

    system("pause");
    return 0;
}

2D array size

4.2.2 Essence of Two-Dimensional Arrays

2D essence

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    char arr[4][30] = {"22222", "11111", "aaaaa", "bbbbb"};
    printf("arr = %d\n", arr);              // address of first row (same as first element)
    printf("&arr = %d\n", &arr);            // address of entire 2D array
    printf("arr[0] = %d\n", arr[0]);       // address of first element of first row
    printf("&arr[0][0] = %d\n", &arr[0][0]); // same

    for (int i = 0; i < 4; i++) {
        printf("%s\n", arr[i]); // arr+i or *(arr+i) also work
    }
    system("pause");
    return 0;
}

2D addresses

4.2.3 Double Pointer as Input (Second Model: 2D Array)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void sort_strings_2d(char arr[][30], int n) {
    // If using **arr, pointer arithmetic would change (step of 4 bytes)
    char tmp[30];
    for (int i = 0; i < n - 1; i++) {
        for (int j = i + 1; j < n; j++) {
            if (strcmp(arr[i], arr[j]) > 0) {
                strcpy(tmp, arr[i]);
                strcpy(arr[i], arr[j]);
                strcpy(arr[j], tmp);
            }
        }
    }
}

void print_strings_2d(char arr[][30], int n) {
    for (int i = 0; i < n; i++) {
        printf("%s, ", arr[i]);
    }
    printf("\n");
}

int main(void) {
    char arr[4][30] = {"22222", "11111", "aaaaa", "bbbbb"};
    int n = sizeof(arr) / sizeof(arr[0]);
    print_strings_2d(arr, n);
    sort_strings_2d(arr, n);
    print_strings_2d(arr, n);
    system("pause");
    return 0;
}

4.3 Pointer to Array (Array Pointer)

array pointer 1 array pointer 2 array pointer 3 array pointer 4 array pointer 5 array pointer 6 array pointer 7 array pointer 8

4.3.1 Two-Dimensional Array Stored as One-Dimensional

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

void print_array(int *arr, int n) {
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void) {
    int arr[][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    print_array((int*)arr, sizeof(arr) / sizeof(arr[0][0]));

    system("pause");
    return 0;
}

2D as 1D

4.3.1.1 Array Pointer with 2D Arrays

array pointer with 2D array pointer with 2D2

5. Structures

5.1 Structure with Nested Pointer (Single Level)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    char *name; // no memory allocated initially
    int age;
} Teacher;

int main(void) {
    // Method 1: struct variable on stack
    Teacher t1;
    t1.name = (char*)malloc(30);
    strcpy(t1.name, "lily");
    t1.age = 22;
    printf("name=%s, age=%d\n", t1.name, t1.age);
    free(t1.name);
    t1.name = NULL;

    // Method 2: struct pointer on heap
    Teacher *p = (Teacher*)malloc(sizeof(Teacher));
    p->name = (char*)malloc(30);
    strcpy(p->name, "lily");
    p->age = 22;
    printf("name=%s, age=%d\n", p->name, p->age);
    free(p->name);
    free(p);
    p = NULL;

    // Method 3: array of structs on heap
    Teacher *arr = (Teacher*)malloc(sizeof(Teacher) * 3);
    for (int i = 0; i < 3; i++) {
        arr[i].name = (char*)malloc(30);
        sprintf(arr[i].name, "name%d%d%d", i, i, i);
        arr[i].age = 20 + i;
    }
    for (int i = 0; i < 3; i++) {
        printf("%s, %d\n", arr[i].name, arr[i].age);
    }
    for (int i = 0; i < 3; i++) {
        free(arr[i].name);
        arr[i].name = NULL;
    }
    free(arr);
    arr = NULL;

    system("pause");
    return 0;
}

memory diagram

5.1.2 Structure as Function Parameter (Encapsulated)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    char *name;
    int age;
} Teacher;

void show_teachers(const Teacher *teachers, int n) {
    for (int i = 0; i < n; i++) {
        printf("%s, %d\n", teachers[i].name, teachers[i].age);
    }
}

void free_teachers(Teacher *teachers, int n) {
    for (int i = 0; i < n; i++) {
        if (teachers[i].name) {
            free(teachers[i].name);
            teachers[i].name = NULL;
        }
    }
    if (teachers) {
        free(teachers);
        teachers = NULL;
    }
}

Teacher* create_teachers(int n) {
    Teacher *teachers = (Teacher*)malloc(sizeof(Teacher) * n);
    for (int i = 0; i < n; i++) {
        teachers[i].name = (char*)malloc(30);
        sprintf(teachers[i].name, "name%d%d%d", i, i, i);
        teachers[i].age = 20 + i;
    }
    return teachers;
}

int create_teachers2(Teacher **teachers, int n) {
    if (teachers == NULL) return -1;
    Teacher *tmp = (Teacher*)malloc(sizeof(Teacher) * n);
    if (!tmp) return -2;
    for (int i = 0; i < n; i++) {
        tmp[i].name = (char*)malloc(30);
        sprintf(tmp[i].name, "name%d%d%d", i, i, i);
        tmp[i].age = 20 + i;
    }
    *teachers = tmp;
    return 0;
}

int main(void) {
    Teacher *p = NULL;
    int ret = create_teachers2(&p, 3);
    if (ret == 0) {
        show_teachers(p, 3);
        free_teachers(p, 3);
        p = NULL;
    }
    system("pause");
    return 0;
}

structure function

5.2 Structure with Nested Double Pointer

Scenario: One mentor has multiple students.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    char **students; // 2D memory: each student is a string
} Mentor;

int main(void) {
    // Allocate array of 3 pointers
    char **names = (char**)malloc(3 * sizeof(char*));
    for (int i = 0; i < 3; i++) {
        names[i] = (char*)malloc(30);
        strcpy(names[i], "lily");
    }
    for (int i = 0; i < 3; i++) {
        printf("%s\n", names[i]);
    }
    for (int i = 0; i < 3; i++) {
        free(names[i]);
        names[i] = NULL;
    }
    free(names);

    // Mentor with 3 students
    Mentor m;
    m.students = (char**)malloc(3 * sizeof(char*));
    for (int i = 0; i < 3; i++) {
        m.students[i] = (char*)malloc(30);
        strcpy(m.students[i], "lily");
    }
    for (int i = 0; i < 3; i++) {
        printf("%s\n", m.students[i]);
    }
    for (int i = 0; i < 3; i++) {
        free(m.students[i]);
        m.students[i] = NULL;
    }
    free(m.students);

    // Mentor pointer on heap
    Mentor *p = (Mentor*)malloc(sizeof(Mentor));
    p->students = (char**)malloc(3 * sizeof(char*));
    for (int i = 0; i < 3; i++) {
        p->students[i] = (char*)malloc(30);
        strcpy(p->students[i], "lily");
    }
    for (int i = 0; i < 3; i++) {
        printf("%s\n", p->students[i]);
    }
    for (int i = 0; i < 3; i++) {
        free(p->students[i]);
        p->students[i] = NULL;
    }
    free(p->students);
    free(p);
    p = NULL;

    // Array of 3 mentors, each with 3 students
    Mentor *mentors = (Mentor*)malloc(sizeof(Mentor) * 3);
    for (int i = 0; i < 3; i++) {
        mentors[i].students = (char**)malloc(3 * sizeof(char*));
        for (int j = 0; j < 3; j++) {
            mentors[i].students[j] = (char*)malloc(30);
            sprintf(mentors[i].students[j], "name_%d_%d", i, j);
        }
    }
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%s ", mentors[i].students[j]);
        }
        printf("\n");
    }
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 3; j++) {
            free(mentors[i].students[j]);
            mentors[i].students[j] = NULL;
        }
        free(mentors[i].students);
        mentors[i].students = NULL;
    }
    free(mentors);
    mentors = NULL;

    system("pause");
    return 0;
}

nested double pointer

5.2.1 Strengthening: Encapsulated Functions

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    char **students;
} Mentor;

int create_mentors(Mentor **mentors, int num_mentors, int num_students) {
    if (mentors == NULL) return -1;
    Mentor *tmp = (Mentor*)malloc(sizeof(Mentor) * num_mentors);
    if (!tmp) return -2;
    for (int i = 0; i < num_mentors; i++) {
        tmp[i].students = (char**)malloc(sizeof(char*) * num_students);
        for (int j = 0; j < num_students; j++) {
            tmp[i].students[j] = (char*)malloc(30);
            sprintf(tmp[i].students[j], "name_%d_%d", i, j);
        }
    }
    *mentors = tmp;
    return 0;
}

void show_mentors(const Mentor *mentors, int num_mentors, int num_students) {
    for (int i = 0; i < num_mentors; i++) {
        for (int j = 0; j < num_students; j++) {
            printf("%s ", mentors[i].students[j]);
        }
        printf("\n");
    }
}

void free_mentors(Mentor **mentors, int num_mentors, int num_students) {
    if (mentors == NULL || *mentors == NULL) return;
    Mentor *tmp = *mentors;
    for (int i = 0; i < num_mentors; i++) {
        for (int j = 0; j < num_students; j++) {
            if (tmp[i].students[j]) {
                free(tmp[i].students[j]);
                tmp[i].students[j] = NULL;
            }
        }
        if (tmp[i].students) {
            free(tmp[i].students);
            tmp[i].students = NULL;
        }
    }
    free(tmp);
    *mentors = NULL;
}

int main(void) {
    Mentor *mentors = NULL;
    int ret = create_mentors(&mentors, 3, 3);
    if (ret == 0) {
        show_mentors(mentors, 3, 3);
        free_mentors(&mentors, 3, 3);
    }
    system("pause");
    return 0;
}

5.3 Deep Copy vs Shallow Copy of Structures

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct {
    char *name;
    int age;
} Person;

int main(void) {
    Person p1;
    p1.name = (char*)malloc(30);
    strcpy(p1.name, "lily");
    p1.age = 22;

    Person p2;
    // Shallow copy: p2 = p1; // both point to same memory; double free later
    // Deep copy: allocate separate memory
    p2.name = (char*)malloc(30);
    strcpy(p2.name, p1.name);
    p2.age = p1.age;

    printf("p2: name=%s, age=%d\n", p2.name, p2.age);

    if (p1.name) {
        free(p1.name);
        p1.name = NULL;
    }
    if (p2.name) {
        free(p2.name);
        p2.name = NULL;
    }

    system("pause");
    return 0;
}

shallow vs deep

Tags: C programming pointers Memory Management Arrays

Posted on Wed, 13 May 2026 16:02:19 +0000 by geroid