What one seems to run into, repeatedly, with C array/pointers is that one can’t get away from needing to know where a piece of memory is from. Returning a pointer to something allocated on the stack? That’s an error.
In most languages, you’re blissfully unaware of stack, heap, and static memory. It doesn’t matter. (On the flip side, you cede control of these things, which can matter from a performance perspective.) In C, you can have things that look similar but are different based on context.
For example, a heap-allocated “array” doesn’t exist as anything but a pointer at compile time, so sizeof(a) will be the size of a pointer, not that of the array. On the other hand, an array of fixed size that lives in a struct (and that’s the only kind of array that can live inline in a struct, because every struct type has to have a fixed total size) will have a known size at compile time.
String constant behavior is also unexpected for many use cases (you have to use strcpy if you want a modifiable string) but this is the price of C’s efficiency. You get exactly what you tell the machine you want (unless it’s illegal, and then you get a segfault) even if you didn’t know that you told it that. My personal view is that string (which is only represented as an array of char for legacy reasons, as we all know that char is too small to fit a modern character) as a concept deserves to be immutable. Of course, the type char * isn’t used only for strings but also for modifiable byte arrays.
String constant behavior is also unexpected for many use cases (you have to use strcpy if you want a modifiable string) but this is the price of C’s efficiency.
Perhaps I’m misunderstanding you.
Are you saying that if you want a char * which is safe to modify, then you need to use strcpy() (or one of its related functions)?
If so, then that is correct; however, if you are instead saying that the only way to get a modifiable string at all is through the above method, that is definitely not true:
char modifiable_str  = "thingsandstuff";
modifiable_str is a string (that you can safely pass to any stdlib string function) which can be safely modified (its type is char).
This is definitely one of those things in C which is kept around for legacy reasons which is a real bummer.
char * being the type of a string literal predates the inclusion of const in the language-proper.
The whole situation would be much better if string literals had the type const char .
const char 
I meant string constants that live in static memory. You’re right that other ways of defining/using string constants don’t have that problem.
However, it’s not intuitive to most people when this is in play. For example, if you return a string constant from a function (e.g. return "Cat") and a client function modifies it, you get a crash (at least on my machine).
“String literals aren’t constant”
Err, yes they are, you just haven’t got the gcc flag turned on that forbids non-const string literals.