C Language – Code Analysis Practice (Intermediate Examples)

1. Intermediate C Code Analysis Examples

1.1. Pointer Size and String Length

  • Understand how a character pointer that points to a string literal behaves.
  • Confirm that sizeof and strlen calculate values using different criteria.
#include <stdio.h>
#include <string.h>

int main(void) {
    char* text = "0123456789";

    size_t result = sizeof(text) + strlen(text);
    printf("%zu\n", result);

    return 0;
}
  • char *text is not an array that stores the string contents; it is a pointer that stores the starting address of a string literal.
  • sizeof(text) is the size of the pointer variable itself, which is different from the length of the string it points to.
  • strlen(text) counts the number of characters from the location the pointer references until it reaches '\0'.
  • The size of a pointer can vary depending on the execution environment, and this explanation uses a 64-bit environment as its basis.

1.2. Pointer Arithmetic

  • An array name behaves like a value that represents the address of the first element, so it can be used like a pointer.
  • Adding +1 to a pointer does not simply move it by 1 byte; it moves by the size of the data type and points to the next array element.
  • In other words, the array name acts as the starting address, and pointer arithmetic has the same meaning as moving to the next slot in the array.
#include <stdio.h>

int main(void) {
    int nums[] = {4, 8, 15, 16, 23, 42};
    int *p = nums + 1;

    int result = *(p + 2) + p[-1];
    printf("%d\n", result);

    return 0;
}
  • nums + 1 means the address of the second element in the array.
  • *(p + n) and p[n] are interpreted in the same way.
  • Negative indexes such as p[-1] are also calculated relative to the current pointer position.

1.3. Pointers as Function Arguments

  • Understand that function arguments in C are passed by value.
  • Understand why passing a pointer as an argument can allow the caller's variable to be modified.
  • Compare how a variable passed by value and a variable passed by address differ after a function call.
#include <stdio.h>

static void update(int *left, int right) {
    right += *left;
    *left = right - (*left / 2);
}

int main(void) {
    int x = 8;
    int y = 3;

    update(&x, y);

    printf("%d\n", x + y);
    return 0;
}
  • &x is the address of the variable x, and the original x can be accessed inside the function through *left.
  • A value received as an ordinary integer argument does not affect the caller's variable even if it changes inside the function.

1.4. Double-Pointer Allocation

  • Understand how to pass the address of dynamically allocated memory back to the caller from inside a function.
  • Understand the flow of using char ** to modify the caller's char * variable.
  • Trace the process of copying a string into the allocated buffer and freeing it after use.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int make_copy(char **out, const char *src) {
    size_t len = strlen(src);

    *out = malloc(len + 1);
    if (*out == NULL) {
        return -1;
    }

    memcpy(*out, src, len + 1);
    (*out)[0] = 'A';

    return (int)len;
}

int main(void) {
    char *buff = NULL;

    int result = make_copy(&buff, "abcde");
    if (result < 0) {
        return 1;
    }

    printf("%s,%d\n", buff, result);

    free(buff);
    return 0;
}
  • &buff is the address of the pointer variable buff itself, and it can be received by a char ** parameter.
  • *out = malloc(...) makes the caller's buff point to the newly allocated memory.
  • You must allocate strlen(src) + 1 bytes so the null character can also be copied.
  • memcpy(..., len + 1) copies both the string body and the final '\0'.
  • (*out)[0] accesses the first character of the allocated string.

1.5. Buffer Size and String Accumulation

  • Understand how to calculate the remaining buffer space when appending a new string after an existing string.
  • Understand how the append function copies data only within the remaining available space.
  • Trace how multiple function calls accumulate changes in the same buffer state.
#include <stdio.h>
#include <string.h>

static int append_limited(char *dst, size_t dst_size, const char *src) {
    size_t dst_len = strlen(dst);
    size_t src_len = strlen(src);

    if (dst_len + src_len >= dst_size) {
        size_t room = dst_size - dst_len - 1;

        memcpy(dst + dst_len, src, room);
        dst[dst_size - 1] = '\0';
        return -1;
    }

    memcpy(dst + dst_len, src, src_len + 1);
    return 0;
}

int main(void) {
    char buffer[10] = "abcd";

    append_limited(buffer, sizeof(buffer), "efgh");
    append_limited(buffer, sizeof(buffer), "ijkl");

    printf("%s\n", buffer);
    return 0;
}
  • When appending to the end of a string, you must calculate the current string length and the total buffer size so the buffer is not exceeded.
  • You must also leave space for the null character ('\0') that marks the end of the string.
  • When strings are appended multiple times, each new result continues accumulating from the previous result.
  • The actual append position points to the end of the current string, expressed as dst + dst_len.