<p>C has the behaviour of doing an implicit reference to element 0 of an array whenever you attempt to use it directly. This extends to passing arrays to/from functions - they get passed by pointer and not by value.</p>
<pre class="lang-c prettyprint-override"><code>int array[5] = {0};
int* pointer_to_array = array;
int* pointer_2_array = &amp;array[0];
bool is_true = (pointer_to_array == pointer_2_array);
</code></pre>
<p>This behaviour has always astounded me.
Even more unusual is that wrapping the array in a struct/union circumvents this.</p>
<p>Breaking the question down into parts:</p>
<ol>
<li>What was the original reason for this in C? I presume C++ just carried it forward.</li>
<li>What justification might a language have for doing this today? (I can't think of an example that comes close aside from Java. Surely there are some?)</li>
</ol>

<language-design><c><history>
What's the justification for implicitly casting arrays to pointers (in the C language family)?
CC BY-SA 4.0
2024-12-12T11:59:06.903Z
Early versions of C did not allow passing or returning structs by value, so your "even more unusual" did not arise.
Nate Eldredge
CC BY-SA 4.0
2024-12-13T12:43:41.517Z
The pointer part came first, arrays are built out of that, not the other way around. And once you understand that, this behavior seems a lot less unusual or illogical. And, FWIW, this also is much closer to how most systems actually implement this in assembly/machine code, it’s almost always some form of indirect addressing, usually indirect with offset. The only exceptions are cases where that’s not possible, or insanities like the iAPX 432 where you have typing and simple data structures implemented all the way down at the hardware level.
Austin Hemmelgarn
<p>It was done that way because it's representative of how the computer actually works. A C-style array is a series of values of the same type arranged contiguously in memory. To access a given element of an array you need only two things:</p>
<ol>
<li><p>The type of the elements, so you know how big each one is (also so that you can do something useful with them, in most cases).</p>
</li>
<li><p>The address of element 0.</p>
</li>
</ol>
<p>Then you can access the <em>n</em>th element by taking the address of element 0 and adding <em>n</em> times the size of a single element.</p>
<p>A pointer to element 0 contains 100% of this information: the address is the value of the pointer, and the type is a fact known to the compiler, which it uses to multiply the offset by a constant in appropriate places. There simply is nothing more to know about an array.</p>
<p>Well, okay, there is one thing, and that's how long the array is. But C, being designed to both run and compile on computers with a handful of kilobytes of memory, considers it the programmer's job to write correct code that only accesses arrays in-bounds, and not the compiler's job to do book-keeping to enforce that fact. If a function needs to be told the size of an array, you can pass it as a separate integer; if it's a compile-time constant, or if you can deduce that accesses will always be in-bounds because of some other property of your code (like &quot;arrays always end with a sentinel&quot;) then that's fine too. C doesn't presume to know. And if you write incorrect code... well, you get incorrect behavior.</p>
<p>So why is there even a sized array type to &quot;decay&quot; into a pointer to begin with? Merely to assist with <em>making space</em> for the elements of the array. If you have <code>int foo[10];</code> in a function, it means to make space for 10 ints on the stack each time the function is called, and to point <code>foo</code> to the beginning of that space. As a static variable it means to reserve the same space in the BSS segment. In a <code>struct</code>, it means that the structure itself is bigger by 10 ints plus padding. Once that work is done, the size is no longer important to C (see the paragraph above), and the only salient thing is the address of element 0.</p>
<p>When you use dynamically-allocated memory (<code>malloc</code>) to make an array, even that space-reservation work is taken out of the language's purview and put into yours, and so you get the pointer back directly, and there's no array type in sight. But you can pass that to a function in just like you can with a static or stack-allocated <code>int[10]</code>, and it will work just the same, because &quot;pointer decay&quot; erases the distinction between them — to your benefit.</p>

CC BY-SA 4.0
2024-12-14T09:25:40.723Z
2024-12-14T09:31:11.347Z
2024-12-14T09:31:11.347Z
hobbs
CC BY-SA 4.0
2024-12-14T23:58:32.667Z
An important distinction between Ritchie's language and the C Standard is that given `int arr[5][3];`, the fact that `arr[1][0]` was guaranteed to immediately follow `arr[0][2]` wasn't just an implementation detail; it also meant that for any `i` in the range 0 to 14, an access to lvalue `arr[0][i]` *was* a perfectly legitimate access to `arr[i/3][i%3]`.
supercat
CC BY-SA 4.0
2024-12-15T21:12:55.567Z
This answer has been really helpful for me to reason about C "arrays" better. Thank you. Is the reason you can use the array notation in function declarations specifically for multidimensional support, then? It confuses me why it would be allowed otherwise
Icedude_907
CC BY-SA 4.0
2024-12-16T04:11:17.657Z
@Icedude_907 I'm honestly unsure but that's a fair guess. I wasn't thinking much about multidimensional arrays when I wrote this. But yes, for multi-dimensional arrays all but the first element of the shape are needed when indexing into it, and that does seem to warrant array-typed parameters.
hobbs
<p>One of the big motivations behind C (and perhaps <em>the</em> reason it became so ubiquitous) is that it's more or less a portable assembly language.  It provides just enough abstraction for your code to work on multiple platforms, but doesn't provide so much abstraction that you lose the ability to look at the C code and predict what the generated assembly would look like. A number of C's design decisions were made because they mirrored the functionality available at the hardware level.</p>
<p>Regarding arrays, it's important to remember that C is not an object-oriented language.  Compound objects like arrays or structs do not actually exist.  The only data types that the vast majority of CPUs understand are integers of varying sizes, floating-point numbers, and pointers (which are arguably just integers).  Everything else in the C language is syntax sugar.</p>
<p>Arrays don't decay into pointers when you pass them to a function.  Your &quot;array&quot; never was anything other than a pointer in the first place.  When you work with arrays in assembly, you do your own pointer arithmetic.  For example, an array dereference in MIPS assembly language might look like:</p>
<pre><code>lw <span class="math-container">$4, 8($</span>3)
</code></pre>
<p>Register 3 holds the base address of the array. This instruction takes that base address, adds an offset of 8 bytes to it to form the target address, and then copies the value from that target address into register 4.  C's syntax is a thin veneer over this.  The array/pointer in your code is your base address.  The compiler calculates the offset for you based on your array index and the size of each array entry.</p>
<p>Structures work essentially the same way.  When you declare a structure, the compiler calculates the offset to each member.  The <code>struct</code> in your code is merely a pointer.  When you reference a struct member, the compiler simply adds that pre-computed offset to the value of the pointer.</p>
<p>When you think of these things as merely being shorthand for pointer arithmetic, many of C's other details make more sense as well.</p>
<ul>
<li>The <code>offsetof()</code> macro merely pulls values from the compiler's internal table of pre-computed offsets.</li>
<li>You can't access the members of a struct when no definition is in scope, because the compiler hasn't computed an offset for that member yet. You can use pointers to a structure after only a forward reference because that's enough information for the compiler to know it's just a pointer like any other.</li>
<li>Arrays appear to be passed &quot;by reference&quot; to functions. Technically it's just a pointer, and you're passing the pointer <em>by value</em> like any other integer-like data type.</li>
<li>A struct can be passed to a function &quot;by value&quot; because the compiler knows the size of the structure (i.e., how many bytes to copy). Struct assignment <code>b = a</code> is shorthand for <code>memcpy(&amp;b, &amp;a, sizeof(a))</code>.</li>
<li>You can declare a loose pointer like <code>int* ptr = 0xFF000000;</code> and then use it like <code>ptr[14] = 0;</code> even though it's not an array.  The compiler is simply doing the pointer arithmetic you asked it to do. This is also the basis for many types of type punning.</li>
<li><code>sizeof()</code> doesn't work on dynamically-allocated arrays or arrays passed into a function. A pointer doesn't contain any length information, so the compiler has no way to know the size when given only a base address. <code>sizeof()</code> only works if the array's definition is in the current scope since the compiler can remember how much space it allocated.</li>
<li><code>union</code> is handled essentially the same as <code>struct</code> except each member's offset is calculated relative to the base address instead of relative to the end of the previous member.</li>
</ul>
<p>C was designed to be very close to the metal. The programmer was responsible for things like keeping track of array bounds, ensuring pointers were compatible with underlying data, etc., which is the way it was in assembly. C was a step <em>forward</em> in that respect, moving some of that burden from the programmer to the compiler.</p>
<p>Modern languages really do spoil us. We have a tremendous amount of computing resources available both at compile time and at run time. We can treat arrays like first-class objects, and the runtime engine will remember their size for us, throw an exception if we exceed their bounds, etc.  It's easy to forget just how limited and primitive computers used to be.  Some old C compilers only had access to enough memory to remember the first 8 characters of a variable or function name, everything after that was ignored.  The preprocessor, compiler, linker, etc. were all separate programs, and compiling a single .c file would require multiple passes and lots of intermediate files.  C was originally implemented on a PDP-7 computer, which came standard with just 9KB of memory.  The things we take for granted now were either wildly impractical or not yet invented.</p>

CC BY-SA 4.0
2024-12-13T19:58:37.567Z
2024-12-13T19:58:37.567Z
bta
CC BY-SA 4.0
2024-12-13T22:39:45.750Z
**Arrays don't decay into pointers when you pass them to a function.** Yes, they do. Arrays decay to pointers whenever they're used as rvalues. See https://stackoverflow.com/questions/1461432/what-is-array-to-pointer-conversion-aka-decay
Barmar
CC BY-SA 4.0
2024-12-13T23:47:59.217Z
@Barmar You seem to have skipped over the next sentence
Bergi
CC BY-SA 4.0
2024-12-13T23:50:22.677Z
That sentence is also wrong. Arrays are not pointers. You can assign to pointers, to make them point somewhere else, but you can't assign to arrays. An array of pointers is not the same as a 2-dimensional array. `sizeof` treats them differently.
Barmar
CC BY-SA 4.0
2024-12-14T04:01:29.750Z
@Barmar You can only assign to lvalues, which pointers are generally not: int *x; (x+1)=5
Remember Monica
CC BY-SA 4.0
2024-12-14T06:04:29.403Z
@RememberMonica `int arr[5]; int *p = arr; p = arr+1; /*OK*/ arr = p+1; /* not OK */`
Barmar
CC BY-SA 4.0
2024-12-14T11:03:36.757Z
I don't think it's quite right to contrast C only with languages which came *later* - at the time C was invented, Fortran had been around for 20 years, with subscripted variables which *weren't* considered syntax sugar for pointers. The account quoted in my answer suggests Ritchie was well aware of other techniques, but chose not to use them.
IMSoP
CC BY-SA 4.0
2024-12-14T23:48:30.237Z
@IMSoP Fortran didn't have pointers at all, so the issue is moot there. The question only makes sense when comparing with other languages with pointers.
Barmar
CC BY-SA 4.0
2024-12-14T23:55:32.887Z
@Barmar: Fortran did have pointers.  If memory serves, a definition for a pointer included a type and the name of an existing `INTEGER`; accesses performed using the name of the pointer would access the storage whose address was given in the associated integer.
supercat
CC BY-SA 4.0
2024-12-15T11:25:58.940Z
@Barmar I was responding to the assertion in the answer that other ways of handling arrays (which is the primary topic here, not pointers) "were either wildly impractical or not yet invented". It may be reasonable to say "other techniques existed, but were impractical *on the machines Ritchie was working with*", but it's not true to imply that having arrays at all was somehow innovative.
IMSoP
CC BY-SA 4.0
2024-12-15T14:09:12.903Z
@supercat I was never a big Fortran user, and haven't done anything with it in at least 40 years. I'm pretty sure it didn't have anything pointer-like at the time, but I wouldn't be surprised if they've been added since then.
Barmar
CC BY-SA 4.0
2024-12-15T19:27:53.067Z
@Barmar: I think the pointers I described were part of FORTRAN-77.  I think there were restrictions on passing references to arrays whose address was taken, and using pointers to those arrays within functions which also accessed storage via those references, and use of pointers for purposes other than interop with non-FORTRAN constructs was discouraged, but I'm pretty sure they existed.
supercat
<p>Last but not least, arrays are often used to store more than just a handful of items; i.e., their memory footprint is often large.</p>
<p>Passing arrays by value as a general rule would have been a preposterous idea on the machines C, or at least its predecessors, was developed on. The PDP-11 had <a href="https://en.wikipedia.org/wiki/PDP-11" rel="nofollow noreferrer">4k of RAM in its smallest configuration</a>. If we can believe Wikipedia, it had 4k of RAM because the marketing department could be dissuaded from shipping it with 2k.</p>
<p>On such a machine, nothing beyond atomic types was passed by value; and for them  that made sense because they had to, or at least it was advantageous to have them, be in registers in order to work with them. It would make no sense at all to create an array copy <em>in memory</em> by default; a copy that was logically unnecessary in the vast majority of cases (I'm not sure when I have last copied an array around with memcpy, and even then it was probably only partially and involved some marshalling because it was pushing data to some device).</p>
<p>Bulk copying data, be it structs or arrays, was always an explicit operation.</p>

CC BY-SA 4.0
2024-12-15T12:28:23.667Z
2024-12-15T12:28:23.667Z
Peter - Reinstate Monica
CC BY-SA 4.0
2024-12-19T19:46:35.067Z
Was 'double' an atomic type on the PDP-11?
supercat
CC BY-SA 4.0
2024-12-19T22:55:44.063Z
@supercat Well, it was a vague term (in particular, unrelated to std::atomic or any other atomicity with respect to threads). Perhaps a better term would be built-in. But apparently, later PDP-11s had FPUs with 64 bit float registers.
Peter - Reinstate Monica
<p>The unusual implementation of arrays in C is described in a wonderful document called <a href="http://cm.bell-labs.co/who/dmr/chist.html" rel="noreferrer">The Development of the C Language by Dennis Ritchie</a>. (The <a href="http://cm.bell-labs.com/cm/cs/who/dmr/chist.html" rel="noreferrer">more widely used URL</a> seems to have been broken by the current owners of the <code>bell-labs.com</code> domain. )</p>
<p>The short version is that it's a vestige of C's evolution from the earlier languages BCPL and B, which lacked a comprehensive type system, but had a simple way to represent arrays by pointing into a linear array of &quot;cells&quot; in memory.</p>
<p>When C added a more complex type system and more flexible memory layout, this evolved into a rule that an array variable acts as a pointer to the array's first element when directly referred to.</p>
<hr />
<p>C's direct ancestors are BCPL, and an earlier experiment called B. Both are implemented as an array of &quot;memory cells&quot;, and array data-types are implemented as a section of this linear memory:</p>
<blockquote>
<p>Because pointers in BCPL and B are merely integer indices in the memory array, arithmetic on them is meaningful: if p is the address of a cell, then p+1 is the address of the next cell. This convention is the basis for the semantics of arrays in both languages. When in BCPL one writes <code>let V = vec 10</code> or in B, <code>auto V[10];</code>
the effect is the same: a cell named V is allocated, then another group of 10 contiguous cells is set aside, and the memory index of the first of these is placed into V.</p>
<p>[...]</p>
<p>This approach to arrays was unusual even at the time; C would later assimilate it in an even less conventional way.</p>
</blockquote>
<p>This layout persisted into the short-lived NB (&quot;New B&quot;), where arrays and pointers were almost identical:</p>
<blockquote>
<p>Within procedures, the language's interpretation of the pointers was identical to that of the array variables: a pointer declaration created a cell differing from an array declaration only in that the programmer was expected to assign a referent, instead of letting the compiler allocate the space and initialize the cell.</p>
</blockquote>
<p>But this caused problems as the language grew into what Ritchie calls &quot;Embryonic C&quot;:</p>
<blockquote>
<p>Problems became evident when I tried to extend the type notation, especially to add structured (record) types. Structures, it seemed, should map in an intuitive way onto memory in the machine, but in a structure containing an array, there was no good place to stash the pointer containing the base of the array, nor any convenient way to arrange that it be initialized.</p>
<p>[...]</p>
<p>I wanted the structure not merely to characterize an abstract object but also to describe a collection of bits that might be read from a directory. Where could the compiler hide the pointer to name that the semantics demanded? Even if structures were thought of more abstractly, and the space for pointers could be hidden somehow, how could I handle the technical problem of properly initializing these pointers when allocating a complicated object, perhaps one that specified structures containing arrays containing structures to arbitrary depth?</p>
</blockquote>
<p>So the current semantics were introduced:</p>
<blockquote>
<p>The solution constituted the crucial jump in the evolutionary chain between typeless BCPL and typed C. It eliminated the materialization of the pointer in storage, and instead caused the creation of the pointer when the array name is mentioned in an expression. The rule, which survives in today's C, is that values of array type are converted, when they appear in expressions, into pointers to the first of the objects making up the array.</p>
<p>This invention enabled most existing B code to continue to work, despite the underlying shift in the language's semantics. The few programs that assigned new values to an array name to adjust its origin—possible in B and BCPL, meaningless in C—were easily repaired. More important, the new language retained a coherent and workable (if unusual) explanation of the semantics of arrays, while opening the way to a more comprehensive type structure.</p>
</blockquote>

CC BY-SA 4.0
2024-12-12T13:20:49.310Z
2024-12-12T13:26:33.073Z
2024-12-12T13:26:33.073Z
IMSoP
CC BY-SA 4.0
2024-12-12T22:38:06.217Z
Yes, and also loosing any total size or bounds information for arrays in their conversion to pointer to first element, but also making them type-compatible with pointer to any other array regardless of either array's original bounds.
Erik Eidt
CC BY-SA 4.0
2024-12-12T22:59:43.840Z
@ErikEidt C is not, in general, a type-safe language; it lets you reinterpret memory in all sorts of crazy ways. The history as "simplified BCPL with structured types bolted on" explains a lot of that; and of course generations of hackers (in the older sense) have then exploited that fact for hand-optimizing code.
IMSoP
CC BY-SA 4.0
2024-12-12T23:27:13.380Z
Right.  Historical explanations aside, it is missing a mechanism to capture bounds, maybe like slices in other language.
Erik Eidt
CC BY-SA 4.0
2024-12-13T15:22:36.290Z
Through "pointers [...] are merely integer indices in the memory array"  I finally understand why they thought about `p[i]` and `i[p]` being equivalent!!  さとり！
Pablo H
CC BY-SA 4.0
2024-12-13T15:52:23.227Z
"*This approach to arrays was unusual even at the time; C would later assimilate it in an even less conventional way*" - what was the usual approach?
Bergi
CC BY-SA 4.0
2024-12-13T19:03:00.210Z
@Bergi That sounds like it could be posted as a new question; I guess Fortran and Algol would have been the obvious comparisons at the time, but, I don't know any details of how they defined arrays.
IMSoP
# What's the justification for implicitly casting arrays to pointers (in the C language family)?
Posted by: username_0 on 2024-12-12
C has the behaviour of doing an implicit reference to element 0 of an array whenever you attempt to use it directly. This extends to passing arrays to/from functions - they get passed by pointer and not by value.
int array[5] = {0};
int* pointer_to_array = array;
int* pointer_2_array = &array[0];
bool is_true = (pointer_to_array == pointer_2_array);

This behaviour has always astounded me.
Even more unusual is that wrapping the array in a struct/union circumvents this.
Breaking the question down into parts:

What was the original reason for this in C? I presume C++ just carried it forward.
What justification might a language have for doing this today? (I can't think of an example that comes close aside from Java. Surely there are some?)
**Tags:** language-design c history
> **Comments:**
> **username_1**: Early versions of C did not allow passing or returning structs by value, so your "even more unusual" did not arise.  
> **username_2**: The pointer part came first, arrays are built out of that, not the other way around. And once you understand that, this behavior seems a lot less unusual or illogical. And, FWIW, this also is much closer to how most systems actually implement this in assembly/machine code, it’s almost always some form of indirect addressing, usually indirect with offset. The only exceptions are cases where that’s not possible, or insanities like the iAPX 432 where you have typing and simple data structures implemented all the way down at the hardware level.  
---
## 4 answers
### Answer by username_9 on 2024-12-12. Score: 30 | Accepted answer
The unusual implementation of arrays in C is described in a wonderful document called [The Development of the C Language by Dennis Ritchie](http://cm.bell-labs.co/who/dmr/chist.html). (The [more widely used URL](http://cm.bell-labs.com/cm/cs/who/dmr/chist.html) seems to have been broken by the current owners of the `bell-labs.com` domain. )

The short version is that it's a vestige of C's evolution from the earlier languages BCPL and B, which lacked a comprehensive type system, but had a simple way to represent arrays by pointing into a linear array of "cells" in memory.

When C added a more complex type system and more flexible memory layout, this evolved into a rule that an array variable acts as a pointer to the array's first element when directly referred to.

---

C's direct ancestors are BCPL, and an earlier experiment called B. Both are implemented as an array of "memory cells", and array data-types are implemented as a section of this linear memory:

> Because pointers in BCPL and B are merely integer indices in the memory array, arithmetic on them is meaningful: if p is the address of a cell, then p+1 is the address of the next cell. This convention is the basis for the semantics of arrays in both languages. When in BCPL one writes `let V = vec 10` or in B, `auto V[10];`
> the effect is the same: a cell named V is allocated, then another group of 10 contiguous cells is set aside, and the memory index of the first of these is placed into V.
>
> [...]
>
> This approach to arrays was unusual even at the time; C would later assimilate it in an even less conventional way.

This layout persisted into the short-lived NB ("New B"), where arrays and pointers were almost identical:

> Within procedures, the language's interpretation of the pointers was identical to that of the array variables: a pointer declaration created a cell differing from an array declaration only in that the programmer was expected to assign a referent, instead of letting the compiler allocate the space and initialize the cell.

But this caused problems as the language grew into what Ritchie calls "Embryonic C":

> Problems became evident when I tried to extend the type notation, especially to add structured (record) types. Structures, it seemed, should map in an intuitive way onto memory in the machine, but in a structure containing an array, there was no good place to stash the pointer containing the base of the array, nor any convenient way to arrange that it be initialized.
>
> [...]
>
> I wanted the structure not merely to characterize an abstract object but also to describe a collection of bits that might be read from a directory. Where could the compiler hide the pointer to name that the semantics demanded? Even if structures were thought of more abstractly, and the space for pointers could be hidden somehow, how could I handle the technical problem of properly initializing these pointers when allocating a complicated object, perhaps one that specified structures containing arrays containing structures to arbitrary depth?

So the current semantics were introduced:

> The solution constituted the crucial jump in the evolutionary chain between typeless BCPL and typed C. It eliminated the materialization of the pointer in storage, and instead caused the creation of the pointer when the array name is mentioned in an expression. The rule, which survives in today's C, is that values of array type are converted, when they appear in expressions, into pointers to the first of the objects making up the array.
>
> This invention enabled most existing B code to continue to work, despite the underlying shift in the language's semantics. The few programs that assigned new values to an array name to adjust its origin—possible in B and BCPL, meaningless in C—were easily repaired. More important, the new language retained a coherent and workable (if unusual) explanation of the semantics of arrays, while opening the way to a more comprehensive type structure.
> **Comments:**
> **username_11** (2024-12-12): Yes, and also loosing any total size or bounds information for arrays in their conversion to pointer to first element, but also making them type-compatible with pointer to any other array regardless of either array's original bounds.  
> **username_9** (2024-12-12): @ErikEidt C is not, in general, a type-safe language; it lets you reinterpret memory in all sorts of crazy ways. The history as "simplified BCPL with structured types bolted on" explains a lot of that; and of course generations of hackers (in the older sense) have then exploited that fact for hand-optimizing code.  
> **username_11** (2024-12-12): Right.  Historical explanations aside, it is missing a mechanism to capture bounds, maybe like slices in other language.  
> **username_12** (2024-12-13): Through "pointers [...] are merely integer indices in the memory array"  I finally understand why they thought about `p[i]` and `i[p]` being equivalent!!  さとり！  
> **username_7** (2024-12-13): "*This approach to arrays was unusual even at the time; C would later assimilate it in an even less conventional way*" - what was the usual approach?  
> **username_9** (2024-12-13): @Bergi That sounds like it could be posted as a new question; I guess Fortran and Algol would have been the obvious comparisons at the time, but, I don't know any details of how they defined arrays.  
---
### Answer by username_3 on 2024-12-14. Score: 7
It was done that way because it's representative of how the computer actually works. A C-style array is a series of values of the same type arranged contiguously in memory. To access a given element of an array you need only two things:

1. The type of the elements, so you know how big each one is (also so that you can do something useful with them, in most cases).
2. The address of element 0.

Then you can access the *n*th element by taking the address of element 0 and adding *n* times the size of a single element.

A pointer to element 0 contains 100% of this information: the address is the value of the pointer, and the type is a fact known to the compiler, which it uses to multiply the offset by a constant in appropriate places. There simply is nothing more to know about an array.

Well, okay, there is one thing, and that's how long the array is. But C, being designed to both run and compile on computers with a handful of kilobytes of memory, considers it the programmer's job to write correct code that only accesses arrays in-bounds, and not the compiler's job to do book-keeping to enforce that fact. If a function needs to be told the size of an array, you can pass it as a separate integer; if it's a compile-time constant, or if you can deduce that accesses will always be in-bounds because of some other property of your code (like "arrays always end with a sentinel") then that's fine too. C doesn't presume to know. And if you write incorrect code... well, you get incorrect behavior.

So why is there even a sized array type to "decay" into a pointer to begin with? Merely to assist with *making space* for the elements of the array. If you have `int foo[10];` in a function, it means to make space for 10 ints on the stack each time the function is called, and to point `foo` to the beginning of that space. As a static variable it means to reserve the same space in the BSS segment. In a `struct`, it means that the structure itself is bigger by 10 ints plus padding. Once that work is done, the size is no longer important to C (see the paragraph above), and the only salient thing is the address of element 0.

When you use dynamically-allocated memory (`malloc`) to make an array, even that space-reservation work is taken out of the language's purview and put into yours, and so you get the pointer back directly, and there's no array type in sight. But you can pass that to a function in just like you can with a static or stack-allocated `int[10]`, and it will work just the same, because "pointer decay" erases the distinction between them — to your benefit.
> **Comments:**
> **username_4** (2024-12-14): An important distinction between Ritchie's language and the C Standard is that given `int arr[5][3];`, the fact that `arr[1][0]` was guaranteed to immediately follow `arr[0][2]` wasn't just an implementation detail; it also meant that for any `i` in the range 0 to 14, an access to lvalue `arr[0][i]` *was* a perfectly legitimate access to `arr[i/3][i%3]`.  
> **username_0** (2024-12-15): This answer has been really helpful for me to reason about C "arrays" better. Thank you. Is the reason you can use the array notation in function declarations specifically for multidimensional support, then? It confuses me why it would be allowed otherwise  
> **username_3** (2024-12-16): @Icedude_907 I'm honestly unsure but that's a fair guess. I wasn't thinking much about multidimensional arrays when I wrote this. But yes, for multi-dimensional arrays all but the first element of the shape are needed when indexing into it, and that does seem to warrant array-typed parameters.  
---
### Answer by username_5 on 2024-12-13. Score: 2
One of the big motivations behind C (and perhaps the reason it became so ubiquitous) is that it's more or less a portable assembly language.  It provides just enough abstraction for your code to work on multiple platforms, but doesn't provide so much abstraction that you lose the ability to look at the C code and predict what the generated assembly would look like. A number of C's design decisions were made because they mirrored the functionality available at the hardware level.
Regarding arrays, it's important to remember that C is not an object-oriented language.  Compound objects like arrays or structs do not actually exist.  The only data types that the vast majority of CPUs understand are integers of varying sizes, floating-point numbers, and pointers (which are arguably just integers).  Everything else in the C language is syntax sugar.
Arrays don't decay into pointers when you pass them to a function.  Your "array" never was anything other than a pointer in the first place.  When you work with arrays in assembly, you do your own pointer arithmetic.  For example, an array dereference in MIPS assembly language might look like:
lw $4, 8($3)

Register 3 holds the base address of the array. This instruction takes that base address, adds an offset of 8 bytes to it to form the target address, and then copies the value from that target address into register 4.  C's syntax is a thin veneer over this.  The array/pointer in your code is your base address.  The compiler calculates the offset for you based on your array index and the size of each array entry.
Structures work essentially the same way.  When you declare a structure, the compiler calculates the offset to each member.  The struct in your code is merely a pointer.  When you reference a struct member, the compiler simply adds that pre-computed offset to the value of the pointer.
When you think of these things as merely being shorthand for pointer arithmetic, many of C's other details make more sense as well.

The offsetof() macro merely pulls values from the compiler's internal table of pre-computed offsets.
You can't access the members of a struct when no definition is in scope, because the compiler hasn't computed an offset for that member yet. You can use pointers to a structure after only a forward reference because that's enough information for the compiler to know it's just a pointer like any other.
Arrays appear to be passed "by reference" to functions. Technically it's just a pointer, and you're passing the pointer by value like any other integer-like data type.
A struct can be passed to a function "by value" because the compiler knows the size of the structure (i.e., how many bytes to copy). Struct assignment b = a is shorthand for memcpy(&b, &a, sizeof(a)).
You can declare a loose pointer like int* ptr = 0xFF000000; and then use it like ptr[14] = 0; even though it's not an array.  The compiler is simply doing the pointer arithmetic you asked it to do. This is also the basis for many types of type punning.
sizeof() doesn't work on dynamically-allocated arrays or arrays passed into a function. A pointer doesn't contain any length information, so the compiler has no way to know the size when given only a base address. sizeof() only works if the array's definition is in the current scope since the compiler can remember how much space it allocated.
union is handled essentially the same as struct except each member's offset is calculated relative to the base address instead of relative to the end of the previous member.

C was designed to be very close to the metal. The programmer was responsible for things like keeping track of array bounds, ensuring pointers were compatible with underlying data, etc., which is the way it was in assembly. C was a step forward in that respect, moving some of that burden from the programmer to the compiler.
Modern languages really do spoil us. We have a tremendous amount of computing resources available both at compile time and at run time. We can treat arrays like first-class objects, and the runtime engine will remember their size for us, throw an exception if we exceed their bounds, etc.  It's easy to forget just how limited and primitive computers used to be.  Some old C compilers only had access to enough memory to remember the first 8 characters of a variable or function name, everything after that was ignored.  The preprocessor, compiler, linker, etc. were all separate programs, and compiling a single .c file would require multiple passes and lots of intermediate files.  C was originally implemented on a PDP-7 computer, which came standard with just 9KB of memory.  The things we take for granted now were either wildly impractical or not yet invented.
> **Comments:**
> **username_6** (2024-12-13): **Arrays don't decay into pointers when you pass them to a function.** Yes, they do. Arrays decay to pointers whenever they're used as rvalues. See https://stackoverflow.com/questions/1461432/what-is-array-to-pointer-conversion-aka-decay  
> **username_7** (2024-12-13): @Barmar You seem to have skipped over the next sentence  
> **username_6** (2024-12-13): That sentence is also wrong. Arrays are not pointers. You can assign to pointers, to make them point somewhere else, but you can't assign to arrays. An array of pointers is not the same as a 2-dimensional array. `sizeof` treats them differently.  
> **username_8** (2024-12-14): @Barmar You can only assign to lvalues, which pointers are generally not: int *x; (x+1)=5  
> **username_6** (2024-12-14): @RememberMonica `int arr[5]; int *p = arr; p = arr+1; /*OK*/ arr = p+1; /* not OK */`  
> **username_9** (2024-12-14): I don't think it's quite right to contrast C only with languages which came *later* - at the time C was invented, Fortran had been around for 20 years, with subscripted variables which *weren't* considered syntax sugar for pointers. The account quoted in my answer suggests Ritchie was well aware of other techniques, but chose not to use them.  
> **username_6** (2024-12-14): @IMSoP Fortran didn't have pointers at all, so the issue is moot there. The question only makes sense when comparing with other languages with pointers.  
> **username_4** (2024-12-14): @Barmar: Fortran did have pointers.  If memory serves, a definition for a pointer included a type and the name of an existing `INTEGER`; accesses performed using the name of the pointer would access the storage whose address was given in the associated integer.  
> **username_9** (2024-12-15): @Barmar I was responding to the assertion in the answer that other ways of handling arrays (which is the primary topic here, not pointers) "were either wildly impractical or not yet invented". It may be reasonable to say "other techniques existed, but were impractical *on the machines Ritchie was working with*", but it's not true to imply that having arrays at all was somehow innovative.  
> **username_6** (2024-12-15): @supercat I was never a big Fortran user, and haven't done anything with it in at least 40 years. I'm pretty sure it didn't have anything pointer-like at the time, but I wouldn't be surprised if they've been added since then.  
> **username_4** (2024-12-15): @Barmar: I think the pointers I described were part of FORTRAN-77.  I think there were restrictions on passing references to arrays whose address was taken, and using pointers to those arrays within functions which also accessed storage via those references, and use of pointers for purposes other than interop with non-FORTRAN constructs was discouraged, but I'm pretty sure they existed.  
---
### Answer by username_10 on 2024-12-15. Score: 1
Last but not least, arrays are often used to store more than just a handful of items; i.e., their memory footprint is often large.

Passing arrays by value as a general rule would have been a preposterous idea on the machines C, or at least its predecessors, was developed on. The PDP-11 had [4k of RAM in its smallest configuration](https://en.wikipedia.org/wiki/PDP-11). If we can believe Wikipedia, it had 4k of RAM because the marketing department could be dissuaded from shipping it with 2k.

On such a machine, nothing beyond atomic types was passed by value; and for them that made sense because they had to, or at least it was advantageous to have them, be in registers in order to work with them. It would make no sense at all to create an array copy *in memory* by default; a copy that was logically unnecessary in the vast majority of cases (I'm not sure when I have last copied an array around with memcpy, and even then it was probably only partially and involved some marshalling because it was pushing data to some device).

Bulk copying data, be it structs or arrays, was always an explicit operation.
> **Comments:**
> **username_4** (2024-12-19): Was 'double' an atomic type on the PDP-11?  
> **username_10** (2024-12-19): @supercat Well, it was a vague term (in particular, unrelated to std::atomic or any other atomicity with respect to threads). Perhaps a better term would be built-in. But apparently, later PDP-11s had FPUs with 64 bit float registers.  
---
