memory safety versus type safety
Two different promises a language can make about correctness are often lumped together, but they guard against different mistakes, and seeing them apart sharpens how you think about C and about safer languages. One promise is that your program only ever touches memory it is allowed to touch. The other is that it only ever treats a piece of data as the KIND of thing it actually is. A language can have one without the other.
Memory safety means every access stays within a valid, live object: no reading or writing past the end of an array, no using a pointer after the thing it pointed to is gone, no dereferencing a null or wild pointer. When memory safety is violated you get exactly the bugs on this page — out-of-bounds access, use-after-free, buffer overflow. Type safety means values are only used as their declared type allows: you cannot quietly reinterpret an integer's bits as a pointer, or call a function through a pointer of the wrong signature, without an explicit, checked conversion. C is famously WEAK on both. Its casts let you reinterpret bytes across types (violating type safety and often strict aliasing), and its complete absence of bounds checking and lifetime tracking lets you violate memory safety with a single stray index or a dangling pointer. That freedom is the source of C's power and of its danger in equal measure.
Why this matters: most of the catastrophic, exploitable bugs in C are memory-safety violations, which is why the industry has pushed hard toward memory-safe languages — Rust enforces memory safety at compile time through ownership and borrow checking, while languages like Java and Go achieve it at runtime with bounds checks and garbage collection. The honest nuance worth keeping: memory safety and type safety reduce whole CLASSES of bugs but eliminate neither logic errors nor every flaw — a memory-safe program can still compute the wrong answer, deadlock, or leak resources. Safety is a floor that removes a category of disasters, not a ceiling that guarantees correctness.
/* memory-safety violation: index past the end */ int a[3]; a[5] = 1; /* type-safety violation: reinterpret an int's bits as a pointer */ int n = 0x1234; int *p = (int *)(intptr_t)n; /* now *p touches address 0x1234 */
The first line breaks memory safety (out of bounds); the second breaks type safety (an integer treated as an address). C permits both.
Memory safety and type safety reduce whole classes of bugs but do not guarantee correctness — a perfectly memory-safe, type-safe program can still have logic bugs, deadlocks, and leaks. Do not sell either as 'no more bugs'.