array-to-pointer decay
Beginners often think an array and a pointer are the same thing in C. They are not — but in most expressions an array quietly turns into a pointer to its first element, and that automatic conversion is called array-to-pointer decay. It is why you can pass an array to a function 'by pointer' without writing & anywhere.
An array like int a[10] is a single object: ten ints sitting in a row, and the name a denotes that whole block. The moment you use a in almost any expression — a + 1, *a, passing a to a function — it decays to int *, a pointer to a[0]. So when you declare void f(int a[]) the parameter is really int *a; the array-ness is lost at the boundary, and inside f, a is just a pointer. The two famous exceptions where decay does NOT happen are sizeof(a), which gives the size of the whole array (40 bytes for ten ints), and &a, which gives the address of the whole array with type int (*)[10] — a pointer to an array, not to an int.
Decay is the source of a classic trap: sizeof works differently on an array and on a pointer. Inside a function that received a decayed array, sizeof(a) is the size of a pointer (8 bytes), not the array — the length did not survive the decay. That is why C functions that take arrays almost always take a separate length parameter; the array cannot tell you how long it is once it has become a pointer.
int a[10]; size_t s1 = sizeof(a); — s1 is 40. But void f(int a[]) { size_t s2 = sizeof(a); } — inside f, s2 is 8 (a pointer), because a decayed at the call. The length 10 was lost.
sizeof on the array gives 40; on the decayed pointer it gives 8.
An array name is not a variable that holds a pointer — there is no separate pointer object to assign to; a = b on two arrays is an error. Decay produces a temporary pointer value in an expression. Because length does not survive decay, always pass and trust an explicit length, never sizeof on a parameter.