Arrays, strings and the string library

Stop confusing arrays with pointers, size buffers correctly, and know which string functions are dangerous and what to use instead.

Array and pointer decay

In almost every expression an array name decays to a pointer to its first element. That is why sizeof inside a function that takes int arr[] returns the pointer size, not the array size — the size information is gone at the call boundary.

#include <stdio.h>

void wrong(int arr[]) {
    /* arr is a pointer here: sizeof is 8, not 40 */
    printf("in function: %zu\n", sizeof arr);
}

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

int main(void) {
    int arr[10] = { 1, 2, 3 };      /* rest are zero */
    printf("in main:     %zu\n", sizeof arr);   /* 40 on a 32-bit int machine */
    printf("elements:    %zu\n", sizeof arr / sizeof arr[0]);

    wrong(arr);
    right(arr, sizeof arr / sizeof arr[0]);
    return 0;
}
  • Pass the length alongside the pointer. There is no portable way to recover it.
  • sizeof is computed at compile time and only works where the array type is still visible.
  • Array indexing is pointer arithmetic plus a dereference: arr[i] is exactly *(arr + i), and arr[-1] compiles happily — it is simply undefined behaviour.

Strings and their pitfalls

C has no string type. A string is a char array ending in a NUL byte, so a buffer of n bytes can hold at most n - 1 characters of text.

char a[] = "hello";      /* 6 bytes, mutable, writable copy */
char *b  = "hello";      /* pointer to read-only literal: do not modify */
const char *c = "hello"; /* the honest declaration */

/* b[0] = 'H';  undefined behaviour, often a segfault */
FunctionProblemUse instead
strcpy(dst, src)No bound on dstKnown length, or snprintf
strcat(dst, src)Overflows when dst is nearly fullTrack the length or use snprintf
strncpy(dst, src, n)Does not NUL-terminate when the source is too longmemcpy plus an explicit NUL, or snprintf
strncatThe size argument counts only appended bytesCompute remaining space manually
strtokModifies its input and is not reentrantstrtok_r or manual scanning
sprintfNo buffer boundsnprintf
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(void) {
    const char *csv = "ada,grace,alan";
    char buf[64];
    snprintf(buf, sizeof buf, "%s", csv);      /* strtok needs a mutable copy */

    char *save = NULL;
    for (char *tok = strtok_r(buf, ",", &save); tok; tok = strtok_r(NULL, ",", &save)) {
        printf("token: %s (len %zu)\n", tok, strlen(tok));
    }

    /* number parsing with error detection */
    char *end = NULL;
    long v = strtol("  42abc", &end, 10);
    if (end == NULL || *end != '\0') puts("trailing junk");

    /* bounded concatenation without the strncat trap */
    char out[16];
    size_t used = (size_t)snprintf(out, sizeof out, "%s", "ada");
    if (used < sizeof out) snprintf(out + used, sizeof out - used, "%s", "!");
    printf("%s\n", out);
    return 0;
}
⚠️
strlen, strcpy and friends stop at the first NUL. A buffer filled from untrusted bytes that lacks a terminator makes them read past the end. Always terminate anything you fill with fread, recv or read and reserve the extra byte in the allocation.

FAQ

Is strlcpy or strcpy_s portable?
Neither is in standard C17. strlcpy is BSD and macOS only; strcpy_s is optional in C11 Annex K and often unimplemented on glibc. A checked snprintf works everywhere.
How big should a char buffer be?
One byte larger than the longest content you intend to store, and pass sizeof buffer as the bound to snprintf or fgets so the limit follows the declaration automatically.

Pointers and memory Dynamic memory patterns and allocation bugs

Last refreshed 2026-09-18.