Also, a disciplined C programmer will always keep the size of the buffer near the buffer itself.
For example, using something like this is perfectly safe (though, we can see the performance hit of those if-statements if used intensively):
struct buffer {
size_t size;
char *data;
};
void buffer_alloc(struct buffer *buf, size_t size) {
if (buf != NULL) {
buf->data = calloc(size, sizeof(char));
if (buf->data != NULL) {
buf->size = size;
}
}
}
void buffer_free(struct buffer *buf) {
if (buf != NULL && buf->data != NULL) {
free(buf->data);
buf->size = 0;
}
}
bool buffer_read(struct buffer buf, size_t offset, char *dest) {
if (dest != NULL) {
if (buf.data != NULL && offset >= 0 && offset < buf.size) {
*dest = buf.data[offset];
return true;
}
}
return false;
}
int main() {
struct buffer buf = {0};
buffer_alloc(&buf, 123);
char c;
buffer_read(buf, 124, &c);
buffer_free(&buf);
return 0;
}
It is possible to write safe C code, but it takes understanding of the language, sometimes of the compiler as well.The whole "There is no programmer who don't make mistakes" argument is fallacious at best.
C is like a chainsaw, there are certain rules on how to use it safely. Yes you can cut limbs with a chainsaw, that does not make the chainsaw useless.