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.
sizeofis 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), andarr[-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 */| Function | Problem | Use instead |
|---|---|---|
strcpy(dst, src) | No bound on dst | Known length, or snprintf |
strcat(dst, src) | Overflows when dst is nearly full | Track the length or use snprintf |
strncpy(dst, src, n) | Does not NUL-terminate when the source is too long | memcpy plus an explicit NUL, or snprintf |
strncat | The size argument counts only appended bytes | Compute remaining space manually |
strtok | Modifies its input and is not reentrant | strtok_r or manual scanning |
sprintf | No buffer bound | snprintf |
#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.Related
Pointers and memory Dynamic memory patterns and allocation bugs
Last refreshed 2026-09-18.