What does impossibility to return arrays actually mean in C?

  • A+
Category:Languages

I'm not trying to replicate the usual question about C not being able to return arrays but to dig a bit more deeply into it.

We cannot do this:

char f[8](void) {      char ret;     ...fill...     return ret; }  int main (int argc, char ** argv) {      char obj_a[10];     obj_a = f();  } 

But we CAN:

struct s { char arr[10]; };  struct s f(void) {      struct s ret;     ...fill...     return ret; }  int main (int argc, char ** argv) {      struct s obj_a;     obj_a = f();  } 

So, I was skimming the ASM code generated by gcc -S and seems to be working with the stack, addressing -x(%rbp) as with any other C function return. Then...

What is it with returning arrays directly? I mean, not in terms of optimization or computational complexity but in terms of the actual capability of doing so without the struct layer.

Extra data: I am using Linux and gcc on a x64 Intel.

 


First of all, yes, you can encapsulate an array in a structure, and then do anything you want with that structure (assign it, return it from a function, etc.).

Second of all, as you've discovered, the compiler has little difficulty emitting code to return (or assign) structures. So that's not the reason you can't return arrays, either.

The fundamental reason you cannot do this is that, bluntly stated, arrays are second-class data structures in C. All other data structures are first-class. What are the definitions of "first-class" and "second-class" in this sense? Simply that second-class types cannot be assigned.

(Your next question is likely to be, "Other than arrays, are there any other second-class data types?", and I think the answer is "Not really, unless you count functions".)

Intimately tied up with the fact that you can't return (or assign) arrays is that there are no values of array type, either. There are objects (variables) of array type, but whenever you try to take the value of one, you get a pointer to the array's first element. [Footnote: more formally, there are no rvalues of array type, although an object of array type can be thought of as an lvalue, albeit a non-assignable one.]

So, quite aside from the fact that you can't assign to an array, you also can't generate a value to assign to an array. If you say

char a[10], b[10]; a = b; 

it's as if you had written

a = &b[0]; 

So we've got a pointer on the right, and an array on the left, and we'd have a massive type mismatch even if arrays somehow were assignable. Similarly (from your example) if we try to write

a = f(); 

and somewhere inside the definition of function f() we have

char ret[10]; ...fill... return ret; 

it's as if that last line said

return &ret[0]; 

and, again, we have no array value to return and assign to a, merely a pointer.

Now, part of your question is probably "Why is it this way?", and also "If you can't assign arrays, why can you assign structures containing arrays?"

What follows is my interpretation and my opinion, but it's consistent with what Dennis Ritchie describes in the paper The Development of the C Language.

The non-assignability of arrays arises from three facts:

  1. C is intended to be syntactically and semantically close to the machine hardware. An elementary operation in C should compile down to one or a handful of machine instructions taking one or a handful of processor cycles.

  2. Arrays have always been special, especially in the way they relate to pointers; this special relationship evolved from and was heavily influenced by the treatment of arrays in C's predecessor language B.

  3. Structures weren't initially in C.

Due to point 2, it's impossible to assign arrays, and due to point 1, it shouldn't be possible anyway, because a single assignment operator = shouldn't expand to code that might take N thousand cycles to copy an N thousand element array.

And then we get to point 3, which really ends up forming a contradiction.

When C got structures, they initially weren't fully first-class either, in that you couldn't assign or return them. But the reason you couldn't was simply that the first compiler wasn't smart enough, at first, to generate the code. There was no syntactic or semantic roadblock, as there was for arrays.

And the goal all along was for structures to be first-class, and this was achieved relatively early on, shortly around the time that the first edition of K&R was going to print.

But the big question remains, if an elementary operation is supposed to compile down to a small number of instructions and cycles, why doesn't that argument disallow structure assignment? And the answer is, yes, it's a contradiction.

I believe (though this is more speculation on my part) that the thinking was something like this: "First-class types are good, second-class types are unfortunate. We're stuck with second-class status for arrays, but we can do better with structs. The no-expensive-code rule isn't really a rule, it's more of a guideline. Arrays will often be large, but structs will usually be small, tens or hundreds of bytes, so assigning them won't usually be too expensive."

So a consistent application of the no-expensive-code rule fell by the wayside. C has never been perfectly regular or consistent, anyway. (Nor, for that matter, are the vast majority of successful languages, human as well as artificial.)

Finally, returning to the side question of "Are there any other second-class types?", I think it's more than a coincidence that functions, like arrays, automatically have their address taken when they are not being used as themselves (that is, as functions or arrays), and that there are similarly no rvalues of function type. But this is mostly an idle musing, because I don't think I've ever heard arrays referred to as "second-class" types in C. (Perhaps they have, and I've forgotten.)

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: