Gareth Owen said:
What if I use the name of an array *typename* in prototype of a
function. This is not, then an object decaying to a pointer, but an
entire type being subverted.
By "typename", do you mean a name introduced by a typedef?
It's an important distinction. The C standard has a syntactic category
"type-name", and it's not just a single identifier; for example,
"int[42]" is a type-name.
And this subversion of declarations *is* unique to function arguments.
// I declare t to be of type T
int is_t_tsized(T t)
{
// so surely, this must be true, yes?
return (sizeof(T) == sizeof(t));
}
Was the above preceded by something like:
typedef int T[42];
?
No, that equality is not necessarily true. Yes, it's weird and
counterintuitive.
int main()
{
T t;
// Well, this makes sense...
bool always_true = (sizeof(T) == sizeof(t));
// Under what "non-special" circumstances does this return false
bool sometime_false = is_t_the_size_of_a_T(t);
// Under what "non-special" circumstances does this compile?
bool wtf_how_does_this_even_compile = is_t_the_size_of_a_T(&t);
}
Oh yes, that's consistent, reasonable, type-safe and in no way
demonstrative that array types in function arguments are weird.
Not special at all.
I've mostly been discussing what the actual language rules are, not
whether they're weird. I've been describing the way the language is
defined, not defending it as a good idea.
An expression of array type (which includes, but is not limited to, an
identifier that's the name of an array object) is, in most but not all
contexts, implicitly converted to ("decays" to) a pointer to the array's
first element. An argument in a function call is just one of the many
contexts in which this conversion occurs; it is not in any way a special
case.
A parameter declared with an array type, even via a typedef (that's a
good point, BTW), is "adjusted" to a pointer type. This is not a
conversion, it's a compile-time adjustment; as a parameter declaration,
"int arr[]" really *means* "int *arr*. This is a distinct rule from the
decay rule above; the language could have had either rule without the
other.
The [] operator requires a pointer operand and an integer operand. (The
pointer operand is often the result of the implicit conversion of an
array name.) It requires *at run time* that the pointer must point to
an element of an array, with a single object being treated as an array
of one element.
That's the way the language is defined, and if your mental model of the
language does not incorporate the above rules, then *either* you're
going to reach incorrect conclusions about the semantics of some C
programs ("incorrect" meaning inconsistent both with the C standard and
with the behavior of real-world compilers), *or* your mental model will
have to include a large number of special cases, making it much more
confusing than the model presented in the standard.
In the mental model I use, expressions of array type and parameters
of array type are special cases (and I can point to the paragraphs
in the standard that rigorously describe those special cases).
Arrays as function arguments and sizeof applied to function
parameters are not special cases.
My mental model works. Does yours?
Let me be clear: I *do not like* the way C handles pointers and arrays.
I particularly dislike the adjustment rule for function parameters,
especially the fact that in a parameter declaration like "int arr[42]",
the "42" is silently ignored.
Taken together, these rules mean that a program like this:
#include <stdio.h>
#include <ctype.h>
void capitalize(char s[]) {
s[0] = toupper(s[0]);
}
int main(void) {
char message[] = "hello, world";
capitalize(message);
puts(message);
}
works correctly, and *can* be "understood" via a mental model that
doesn't recognize the decay of array expressions, the adjustment of
array parameter declarations, or the fact that the [] operator takes a
pointer operand. And that's my biggest problem with the way C handles
arrays and pointers: it can lead learners down a false path.
There are historical reasons for it. In early pre-ANSI (and in fact
pre-K&R1) C, "int p[];" actually defined p as a pointer. Here's a code
snippet from an early version of Dennis Ritchie's C compiler, dated
1972:
int np[], i;
/* ... */
np = lookup();
*np++ = 1;
*np = t;
That's obviously not valid in modern C, but the language's current
handling of arrays and pointers had to evolve from that earlier version
of the language (and from its predecessors B and BCPL) without breaking
too much existing code.