Arrays
Learning Outcomes
- Declare and initialize C arrays.
- Understand that C arrays should be treated as contiguous blocks of memory, not as pointers. Array names are synonymous with the location of the first element in the array.
- Translate array indexing into pointer arithmetic followed by a dereference operation.
- Decay arrays to pointers when used as formal parameters for function definitions or arguments to functions.
🎥 Lecture Video
🎥 Lecture Video
From 9:36 onwards: Arrays are not pointers exampleWe continue our exploration of memory by studying C arrays. On the surface, C arrays seem fairly similar to what you might recognize from Java. In this section, we learn that arrays in C are neither variables nor pointers. When used in C statements, array names often behave like pointer variable names, for reasons we will describe shortly.
To declare an array of two elements without initializing its values, we can use the below statement. This statement declares a block of memory large enough to hold two contiguous ints. It does not initialize values, so we can assume elements contain garbage:
int arr_unitialized[2];
To initialize and declare an array of two elements 795 and 635, in that order:
int arr2[] = {795, 635};
or equivalently
int arr2[2] = {795, 635};
Square-bracket indexing is one way to access elements of the array. Like many languages, C specifies zero-indexed arrays:
arr2[0]; // 795
Array indexing uses pointer arithmetic
Is there another way to access array elements? Yes, otherwise we would not have been so cryptic earlier.
Square-bracket indexing for C arrays is what we call “syntactic sugar”–meaning, it exists for human readability, but the C compiler will translate it to two operations: pointer arithmetic followed by dereference:
The expression arr[i] is equivalent to the expression *(arr+i). The latter treats the array name arr as a pointer, increments it, then dereferences.
Example
Suppose that when compiled, #code-array-indexing below produces the memory layout in #fig-array-indexing. q is a pointer to a 32-bit unsigned integer, while arr is an array, i.e., a 24-byte contiguous block of 32-bit unsigned integers.
#include <stdio.h>
int main () {
uint32_t arr[] = {50, 60, 70}; // 32-bit unsigned array
uint32_t *q = arr;
printf(" *q: %d is %d\n", *q, q[0]);
printf("*(q+1): %d is %d\n", *(q+1), q[1]);
printf("*(q-1): %d is %d\n", *(q-1), q[-1]);
}
Memory layout for #code-array-indexing.
Because square-indexing is syntactic sugar for pointer arithmetic and dereference:
- Line 4: The pointer
qpoints to an unsigned 32-bit integer at address0x100, which is50. Print*q: 50 is 50. - Line 5: Incrementing
qpoints to the next 32-bit unsigned integer. Ifqpoints to the unsigned 32-bit integer at address0x100, then incrementingqpoints to the next 32-bit unsigned integer at address0x104, which is60. Print*(q+1): 60 is 60. - Line 6: Because square-bracket indexing is syntactic sugar, negative indexing does not produce any error. Instead, decrementing
qpoints to the previous 32-bit unsigned integer at address0x9c, which is an unknown value. This line would likely print garbage, e.g.,*(q-1): 32490 is 32490.
Arrays are not pointers
From K&R:
There is one difference between an array name [(such as
a)] and a pointer [(such aspa)] that must be kept in mind. A pointer is a variable, sopa=aandpa++are legal. But an array name is not a variable; constructions likea=paanda++are illegal.
Also from K&R:
The name of an array is a synonym for the location of the initial element.
Pointers and arrays therefore differ in how they behave with the address operator, &. Consider #code-array-addressing:1
int *p, *q, x;
int a[4];
p = &x;
q = a + 1;
*p = 1;
printf("*p:%d, p:%x, &p:%x\n", *p, p, &p);
*q = 2;
printf("*q:%d, q:%x, &q:%x\n", *q, q, &q);
*a = 3;
printf("*a:%d, a:%x, &a:%x\n", *a, a, &a);
With the memory layout in #fig-array-addressing, the output is:
*p:1, p:108, &p:100
*q:2, q:110, &q:104
*a:3, a:10c, &a:10c
Memory layout for #code-array-addressing.
The address of the array a is the address of the array itself, i.e., the address of the large contiguous memory block of ints!
Show Explanation
- We discuss multiple declaration in a previous section.
-
Line 3: The
intpointer,p, is initialized to the address of theintvariablex.- Line 6: Take the value
ppoints to; set it to 1. *pdereferencespand gets the value at address0x108, which is1.pis a pointer variable;p’s value is an address, which is0x108.&pis the address of the variablep, which is0x100.
- Line 6: Take the value
-
Line 4: The
intpointerqis initialized to the result ofa + 1, which is pointer arithmetic! In the expression, the array nameais the address of the first element ina; incrementing by one yields the address of the second element ofa, at0x110.- Line 9: Take the value
qpoints to; set it to 2. *qdereferencesqand gets the value at address0x110, which is2.qis a pointer variable;q’s value is an address, which is0x110.&qis the address of the variableq, which is0x104.
- Line 9: Take the value
-
Line 2: The array
ais a memory block of 4ints. The array starts at address0x10c, which is also the address of its first element.- Line 12: The array name
ais the address of the first element ina; the statement*a = 3;gets this value and sets it to 3. *ais pointer arithmetic followed by a dereference. The array nameais the address of the first element ina; dereferencing gets the element itself, which is3.ais the address of the first element inaby definition, which is0x10c.&agets the address of the arraya, which is0x10c.2
- Line 12: The array name
Array names “decay” with functions
When used with functions, arrays decay to pointers in two ways. We use #code-decay below as an example.
int bar(int arr[], size_t nelems){
… arr[…] …
}
int main(void) {
int a[5], b[10];
…
bar(a, 5);
…
}
1. When used as formal parameters for function definitions. On Line 2 of #code-decay, the definition int arr[] is syntactic sugar for the definition int *arr. We recommend using the latter where possible to avoid confusion.
2. When passed in as arguments to function calls. On Line 7 of #code-decay, the argument a is an array but decays to a pointer when the function is called. This decay effectively passes in the address of a as the first argument of bar.
sizeof with arrays
We’ve discussed sizeof many times. For arrays, the compile-time operator will evaluate to the size of the array, in bytes.2 This observation informs the behavior of #code-array-sizeof:
void mystery(short arr[], int len) {
printf("%d ", len);
printf("%d\n", sizeof(arr));
}
int main() {
short nums[] = {1, 2, 3, 99, 100};
printf("%d ", sizeof(nums));
mystery(nums, sizeof(nums)/sizeof(short));
return 0;
}
Show Answer
Print output: 0 5 8
- In Line 10,
sizeof(nums)is in array’s declared scope. Evaluate to the total array size of fiveshorts, i.e., 10. - In Line 4, the value
lenis the result of evaluatingsizeof(nums)/sizeof(short)inmain, i.e., 10/2 = 5. - In Line 8,
arris a function parameter. The formal declarationshort arr[]is syntactic sugar forshort *arr, soarris a pointer. The size of a pointer is 64 bits, sosizeof(arr)is 8.
In practice, C programmers will commonly use sizeof(nums)/sizeof(short) to count the number of elements in the array nums. Note that nums must be declared in the same scope, otherwise it decays to a pointer.
Arrays are primitive! Reminders
Hopefully this section has convinced you that arrays are relatively primitive constructs:
- Array declarations set aside contiguous blocks in memory.
- Array names are synonymous with the location of the first element in the array.
- Arrays decay to pointers when used as function parameters or function arguments.
We close with a few final reminders of how this primitive nature begets responsible C practices.
Footnotes
-
We thought long and hard about how to explain
&aandsizeof(a)(it involved sitting in a dark room with loud music). Both operations likely boil down to reasonable C design. After all, there must be some way to refer to the address and the size of an array. Instead of erroring, these two expressions are likely the only exception to treating array names as synonymous with the address of the first element. If you, the reader, have a better explanation, we’d love to use it. Submit a pull request! ↩ ↩2