In C, assignment copies the contents of a fixed-size object to another fixed-size object. This is well defined and fairly straightforward to implement for scalar types (integers, floating-point, pointers, complex types since C99). Assignment of structs is nearly as simple; larger ones might require a call to memcpy()
or equivalent, but it's still straightforward since the size and alignment are known at compile time.
Arrays are a different matter. Most array objects have sizes that aren't determined until run time. A good example is argv
. The runtime environment constructs an array of char
for each command-line argument, and an array of char*
containing pointers to the arguments. These are made available to main
via argv
, a char**
, and via the dynamically allocated char[]
arrays that the elements of argv
point to.
C arrays are objects in their own right, but they're not generally accessed as objects. Instead, their elements are accessed via pointers, and code traverses from one element to the next using pointer arithmetic.
Languages can be designed to treat arrays as first-class objects, with assignment -- but it's complicated. As a language designer, you have to decide whether an array of 10 integers and an array of 20 integers are the same type. If they are, you have to decide what happens when you try to assign one to the other. Does it copy the smaller size? Does it cause a runtime exception? Do you have to add a slice operation so you can operate on subsets of arrays?
If int[10]
and int[20]
are distinct types with no implicit conversion, then array operations are inflexible (see Pascal, for example).
All these things can be defined (see Ada), but only by defining higher-level constructs than what's typical in C. Instead, the designers of C (mostly Dennis Ritchie) chose to provide arrays with low-level operations. It's admittedly inconvenient at times, but it's a framework that can be used to implement all the higher-level array operations of any other language.