IMO, pointers are less difficult to comprehend than other abstractions, like lambdas are.

If you know how to walk down a street and stop at the right street number, then you have used pointers. And if you've ever observed that one tall building may "cover" a range of street numbers, such as 200-220, then you should understand how to move from one 4-byte "value" to the next in an array in memory.

Anyway, many more analogies... probably better than this one.

Maybe unions could make using pointers a bit more challenging, but again, tall buildings next to short buildings and so on. We do this kind of pointer calculation in real life.

What's difficult to understand about pointers isn't the concept of a pointer itself, or even * and &, it's the fact that working with pointers requires you to simultaneously understand different abstraction levels. While it's not unique to pointers, and it's in fact the case for most nontrivial programming tasks, what's unique about C is that pointers are so pervasive you can't really do anything if you don't understand how to work with them.

IME languages like Python aren't any easier than C to work with (ignoring UB issues of course), but it's certainly the case that you can probably kinda sorta get your job done with Python even without understanding the first thing of what you're doing, and that's not happening if you write in C.

In C, pointers require you to think deeply about the ownership and lifetime of any "allocated object" at runtime. How long does it live, who is responsible for the deallocation, how many pointers does your program hold to that object (dangling issues). Ultimately, it can lead to a cleaner design if these issues are taken seriously up-front.

I don't disagree with that, but most cases fall within a pretty clear pattern:

- typedef struct { ... } foo

- foo *foo_create()

- void foo_destroy(foo *)

- a bunch of functions that take foo* as their first arg

which is kind of the same as a class and only more error-prone.

I say this as someone who actually _likes_ C, but the manual memory management model is very often unnecessary, confusing, repetitive. There was an idea some time ago of a language extension that would extend the concept of automatic storage duration to allow an explicit destructor to be called when the variable goes out of scope, like variables in some languages. I genuinely think things like that would make the language a bit more ergonomic without fundamentally changing its nature.

I’m actually implementing that right now for my own use, as a pre-processor.

There is a much more advanced design and implementation at “A defer mechanism for C” (December 2020): https://gustedt.wordpress.com/2020/12/14/a-defer-mechanism-f...

For my own purposes, I think I can live without handling stack unwinding so I continue working on my pre-processor.

Since the pre-processor is not yet finished, there I use a vector¹ of {.pointer, .destructor} where I put objects after initialization, with one macro at the end of each managed scope that calls the destructors for that scope in reverse order, then another macro meant for the function exit points that calls all the destructors in that vector. This has been built many times before by other people of course, it’s just an exercise to see which difficulties arise.

¹ Vector, growable array: I did my own trivial type-generic growable array, with the classic {.capacity, .len, .items}, but again there are many previous implementations. The two I’ve found more interesting are:

- “C Template Library (CTL)”: https://github.com/glouw/ctl

- “Klib: a Generic Library in C”: https://github.com/attractivechaos/klib/