Compare dangerous functions with safe replacements. Buffer overflows have been a top security vulnerability for decades.
#include <string.h> int main(void) { char buf[8]; // only 8 bytes strcpy(buf, "This is too long!"); // writes 18 bytes β overflow return 0; }
sizeof or a constant).char dst[8]; strcpy(dst, src); // no size check β overflow if src >= 8 bytes
char dst[8]; strncpy(dst, src, sizeof(dst) - 1); dst[sizeof(dst) - 1] = '\0'; // always terminate explicitly
'\0'. You must put it there yourself.char dst[8]; int n = snprintf(dst, sizeof(dst), "%s", src); // snprintf always NUL-terminates (when size > 0) // return n is "chars it wanted to write"; truncation if n >= sizeof(dst) if (n >= (int)sizeof(dst)) { fprintf(stderr, "warning: truncated\n"); }
char buf[32]; sprintf(buf, "Hello, %s!", name); // long name overflows buf
char buf[32]; snprintf(buf, sizeof(buf), "Hello, %s!", name);
int n = snprintf(buf, sizeof(buf), fmt, ...); if (n < 0) { /* encoding error */ } else if (n >= sizeof(buf)) { /* truncation */ } else { /* OK: n bytes written */ }
gets has no size parameter and was removed in C11. Use fgets instead.char line[64]; gets(line); // NG: no size limit
char line[64]; if (fgets(line, sizeof(line), stdin) == NULL) { // EOF or error }
char line[64]; if (fgets(line, sizeof(line), stdin)) { size_t len = strlen(line); if (len > 0 && line[len - 1] == '\n') { line[len - 1] = '\0'; } }
line[len-1] won't be '\n'. Decide whether to loop to read the rest or report an error.atoi cannot report errors and the behavior on overflow is undefined. strtol lets you detect "conversion failed" and "out of range" correctly.int n = atoi(s); // s="abc" also returns 0 β can't tell from error // s="99999999999" is undefined
#include <stdlib.h> #include <errno.h> errno = 0; char *end; long v = strtol(s, &end, 10); if (end == s) { /* no digits consumed */ } else if (*end != '\0') { /* trailing junk */ } else if (errno == ERANGE) { /* out of range */ } else { /* success: value is v */ }
#include <errno.h> #include <limits.h> #include <stdlib.h> int parse_int(const char *s, int *out) { errno = 0; char *end; long v = strtol(s, &end, 10); if (end == s || *end != '\0') return -1; if (errno == ERANGE || v < INT_MIN || v > INT_MAX) return -1; *out = (int)v; return 0; }
strtod for doubles, strtoul for unsigned long. Same pattern.| Avoid | Use instead | Notes |
|---|---|---|
strcpy | snprintf / strncpy+NUL | No size check |
strcat | strncat / snprintf | Same |
sprintf | snprintf | Detect truncation via return |
gets | fgets | gets removed in C11 |
atoi / atof | strtol / strtod | Error-detecting |
scanf("%s", ...) | fgets+sscanf or width %31s | Unbounded %s is unsafe |
gcc -Wall -Wextra -Wformat-security -Wstack-protector \
-fstack-protector-strong -D_FORTIFY_SOURCE=2 -O2 prog.c
-D_FORTIFY_SOURCE=2 adds runtime checks via glibc. -fstack-protector-strong aborts on stack corruption.cppcheck, clang-tidy, or clang --analyze to catch buffer bugs before they ship.-fstack-protector-strong and observe stack smashing detected.parse_int: one strict version that rejects "123abc", one lenient version that accepts "3.14" and keeps the integer part. Add 10 test cases.char *read_line(FILE *fp, char *buf, size_t size);. If the input exceeds size, discard the rest so the next read starts clean. Return buf or NULL on EOF.