Array elements and valid indices
Which values change
You know how to record the current value of an integer variable. Now suppose four related values belong together. We can store them in an array and still trace every assignment one step at a time.
In this lesson you will identify an array's valid indices, read its initial values, and explain which element an assignment changes. You will also distinguish a saved scalar copy from an array element, and explain the zeros supplied by a partial initializer.
Prerequisites: the foundation lessons on variables, current-state tracing, simple arithmetic, conditions and copied values. No pointer, loop, string or memory-address knowledge is required here.
We continue to use C11 as our teaching convention. Our arrays contain ordinary signed int elements and have explicit positive constant sizes. Every value read in an executable example has been initialized, and every computed integer fits within −32767 through 32767. We do not assume that an int occupies four bytes. Every complete program starts afresh and can be compiled separately. One deliberately unsafe fragment is clearly marked for classification only.
One array name and several element positions
Read int scores[4] = {6, 2, 9, 4}; in two parts. scores[4] in this declaration creates an array with four integer elements. The initializer in braces supplies their starting values in order.
| Index | 0 | 1 | 2 | 3 |
|---|---|---|---|---|
| Initial value | 6 | 2 | 9 | 4 |
An index identifies a position. The first position has index 0, so scores[0] supplies 6 when it is read, and scores[1] supplies 2. The fourth and last element is scores[3]. The declared count is 4; it is not a valid index for this array.
Keep a position and its stored value separate. The value 9 is stored at index 2. That does not give this array an element at index 9. An element may contain a negative number or a number larger than the array's length; the stored number does not change the valid positions.
The same square brackets have different jobs in a declaration and in a later expression. The declaration int scores[4] states the element count. A later read of scores[4] would try to access a fifth element, which does not exist in this array.
For an array with N elements and an integer index i, an element access needs both i >= 0 and i < N. Here N means the element count in our explanation; our actual declarations use positive constants such as 4 and 5. The last valid index is N - 1. Neither a negative index nor an index equal to the count is valid for these accesses.
Apply the familiar assignment rule to one element
In scores[1] = scores[0] + scores[2];, the right side reads two current element values. Their sum is stored in the element at index 1. This statement does not assign to every element.
Use a record with an entry for each position:
- Work out the index of every accessed element and check its bounds
- Read the current values used by the right-hand calculation
- Calculate the result
- Write it to the selected destination element and carry all other entries forward
Our statements keep index changes separate from element updates. We do not combine an index increment and another use of that index inside one expression. This carries forward the foundation lesson's sequencing discipline.
Worked example 1 A saved value and two element updates
Predict the complete output before reading the trace. Does saved change when scores[1] changes?
#include <stdio.h>
int main(void)
{
int scores[4] = {6, 2, 9, 4};
int saved = scores[1];
scores[1] = scores[0] + scores[2];
scores[0] = saved - 1;
printf("%d %d %d %d | %d\n",
scores[0], scores[1], scores[2], scores[3], saved);
return 0;
}- After initialization:
scores = {6, 2, 9, 4} saved = scores[1]reads the current value 2. Nowsaved = 2; the array is unchangedscores[1] = scores[0] + scores[2]computes6 + 9 = 15. Nowscores = {6, 15, 9, 4}andsaved = 2scores[0] = saved - 1computes2 - 1 = 1. Nowscores = {1, 15, 9, 4}andsaved = 2- The printed argument order is the four elements, then
saved. The output is1 15 9 4 | 2, followed by a newline. The|is just a printed separator
All accessed indices are 0 through 3. Each assignment replaces one element's stored value; the other positions keep their values.
saved holds a copied integer. It is not another name for element 1 and does not keep following it. If you predicted 14 15 9 4 | 15, you treated that saved copy as a continuing connection. If you left the first element at 6, you skipped the second element assignment. If you put 15 into every position, you treated one selected element as the whole array.
Partial initialization supplies the remaining zeros
A positive fixed-size integer array can have fewer initializer values than elements. In int bins[5] = {4, -1};, the two supplied numbers initialize positions 0 and 1. The remaining positions 2, 3 and 4 start at zero.
| Index | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Initial value | 4 | −1 | 0 | 0 | 0 |
The size is still five. An omitted initializer does not remove an element, repeat the last written value, or leave that remaining element waiting for input. int ready[3] = {0}; therefore starts all three elements at zero: the first is explicitly initialized to zero and the other two receive zero implicitly.
Do not transfer that rule to int bins[5]; inside main. That declaration has no initializer. Its automatic elements do not receive guaranteed zeros. Assign known values before reading them; do not run an uninitialized read to decide what C “usually” provides. Static-storage arrays have different initialization rules and are outside this lesson.
These initializer lists set starting values once when the declarations are reached. A later assignment does not restart the initialization or refill the other elements with zeros.
Worked example 2 Use an implicitly initialized element
This example combines supplied values and implicit zeros. Work from the state left by each statement.
#include <stdio.h>
int main(void)
{
int bins[5] = {4, -1};
bins[3] = bins[0] + bins[2];
bins[2] = bins[1] - bins[4];
printf("%d %d %d %d %d\n",
bins[0], bins[1], bins[2], bins[3], bins[4]);
return 0;
}- The declaration gives
bins = {4, -1, 0, 0, 0} - For
bins[3] = bins[0] + bins[2], the operands are 4 and 0. Store 4 at index 3:{4, -1, 0, 4, 0} - For
bins[2] = bins[1] - bins[4], the operands are −1 and 0. Store −1 at index 2:{4, -1, -1, 4, 0} - Output:
4 -1 -1 4 0, followed by a newline
The read of bins[2] in the first assignment is valid: its implicit initial value is zero. The later assignment changes that same element to −1. The implicit zero is a starting value, not a permanent constraint.
Five copies of 4, or three trailing copies of −1, would misread the initializer list. Claiming that bins[4] is uninitialized would miss the rule for the remaining elements of this initialized array. Reading bins[5] would still be invalid; partial initialization creates no extra element beyond the declared size.
An index can be calculated
The brackets can contain an integer expression. With a valid value of k, scores[k] selects the position given by the current k, while scores[k - 1] selects the previous position. Check each resulting index separately. A valid k does not automatically make k - 1 or k + 1 valid.
For the four-element scores, k = 0 is valid for scores[k] but not for scores[k - 1]. At k = 3, scores[k] is valid but scores[k + 1] is not. Updating an index variable later does not undo a past array update. It changes which position a subsequent expression selects.
Practice before opening the solutions
These four original learning exercises are unscored. For defined programs, write intermediate array states as well as the output. Question 3 is a classification-and-repair task: do not execute its unsafe fragment.
Practice 1 A moving index
Find the output. Explain both the position changed by the assignment and the position selected after k changes.
#include <stdio.h>
int main(void)
{
int rack[5] = {8, 3, 6, 1, 4};
int k = 2;
rack[k] = rack[k - 1] + rack[k + 1];
k = k + 1;
printf("%d %d %d\n", rack[2], rack[k], k);
return 0;
}Practice 2 Two different initializer lists
Give both final arrays and the printed triple. Does {2} fill an array with copies of 2?
#include <stdio.h>
int main(void)
{
int first[4] = {2};
int second[4] = {2, 2, 2, 2};
first[3] = first[0] + second[1];
second[0] = first[1];
printf("%d %d %d\n", first[3], second[0], first[1]);
return 0;
}Practice 3 Classify and repair the last index
The intention is to add the first and last elements of a three-element array. The following fragment is deliberately unsafe. Read it only; do not compile or execute it as an output question.
int data[3] = {2, 5, 8};
int total = data[0] + data[3];Is a particular numeric value of total prescribed by C11? Identify the invalid access, then change only its index to meet the stated intention. Give the result of the repaired calculation.
Practice 4 A guard before an access
Trace both if statements. Give the final array and explain why the first selected index does not cause an invalid access in this program.
#include <stdio.h>
int main(void)
{
int cells[3] = {4, 6, 8};
int index = -1;
if (index >= 0 && index < 3) {
cells[index] = 9;
}
index = 2;
if (index >= 0 && index < 3) {
cells[index] = 9;
}
printf("%d %d %d\n", cells[0], cells[1], cells[2]);
return 0;
}For a general integer index, would changing the upper test to index <= 3 still protect a three-element array? Explain the boundary that decides the answer.
Full solutions and wrong-turn feedback
Practice 1 solution
Initially k = 2 and rack = {8, 3, 6, 1, 4}. The assignment uses destination index 2 and source indices 2 - 1 = 1 and 2 + 1 = 3. All three are within 0 through 4.
Read rack[1] = 3 and rack[3] = 1; their sum is 4. Store it at index 2, leaving rack = {8, 3, 4, 1, 4}. Next k = k + 1 makes k = 3 without changing the array. The print reads rack[2] = 4, rack[3] = 1 and k = 3. Output: 4 1 3, then a newline.
4 4 3 assumes that rack[k] keeps naming the previously selected element after k changes. 6 1 3 ignores the array assignment. Using 8 and 6 as the sum's operands reads positions 0 and 2 instead of positions 1 and 3; calculate k - 1 and k + 1 again.
Practice 2 solution
The declarations give first = {2, 0, 0, 0} and second = {2, 2, 2, 2}. Only the first array has omitted initializer values.
first[3] = first[0] + second[1] computes 2 + 2 = 4, so first = {2, 0, 0, 4}. Then second[0] = first[1] reads the zero at index 1 of first and stores it in second. The final arrays are first = {2, 0, 0, 4} and second = {0, 2, 2, 2}. Output: 4 0 0, then a newline.
4 2 2 repeats the supplied 2 into omitted positions. 4 4 0 reads first[3] where the code says first[1]. The two arrays remain distinct even when some of their elements have equal values.
Practice 3 solution
The valid indices are 0, 1 and 2. data[3] tries to read outside this array. The fragment has undefined behavior; C11 does not prescribe a numeric value for total. An observed number, zero or a crash would not establish a valid language rule. The partial-initializer rule says nothing about storage outside an array.
Replace data[3] with data[2]. The repaired program is:
#include <stdio.h>
int main(void)
{
int data[3] = {2, 5, 8};
int total = data[0] + data[2];
printf("%d\n", total);
return 0;
}It reads the first value 2 and last value 8, computes 10 and prints 10 followed by a newline. Using data[1] would produce 7 but add the first and middle elements, contrary to the task. Increasing the array size would change the given problem rather than repair only the index.
Practice 4 solution
Start with cells = {4, 6, 8} and index = -1. The first comparison index >= 0 is false. && skips the second comparison, the if condition is false, and the body containing cells[index] is not executed. No array access using −1 occurs.
After index = 2, both comparisons in the second condition are true: 2 is nonnegative and below 3. The body writes 9 to cells[2]. The final array is {4, 6, 9}, and the output is 4 6 9, followed by a newline.
The correct guard for these element accesses requires both boundaries. index <= 3 would allow index 3, which is one beyond the last valid index 2. It happens not to change the two index cases shown, but it is not a valid guard for every integer index. Checking only index < 3 would also admit negative indices. A guard must decide before the element access; a test afterward cannot repair an access that has already occurred.
Before moving on
Explain why the count 4 in a declaration does not make index 4 valid, and why {2} and {2, 2, 2, 2} give different four-element starting states. You are ready for the next lesson when you can follow each element's state without losing the current index. Next we will visit several elements with a loop and explain what a running total or count means.
Source note
The examples, traces, exercises and explanatory wording are original. Facts were checked against the WG14 N1570 C11 committee draft: array types §6.2.5p20; subscripting §6.5.2.1p2; bounds §6.5.6p8; initialization §§6.7.9p10, p17, p21; constant array-size constraint §6.7.6.2p1; simple assignment §6.5.16.1p2; short circuit §6.5.13p4. Reference: https://open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf
C11 is this course's explicit teaching convention, not a claimed GATE-specified version. This lesson covers introductory arrays, not the complete C language or GATE CS syllabus.
Quick reference
Quick reference
- In
int a[4] = {6, 2, 9, 4};, the array has four elements, at indices 0 through 3 - An index selects a position; it is not the value stored there
- For
Nelements, an integer element index must satisfy0 <= iandi < N - Trace one selected destination and carry unchanged elements forward
- A scalar initialized from
a[i]keeps its copied value after that element changes - In a positive fixed-size integer array with an initializer list, omitted elements start at zero
- An automatic array with no initializer does not get guaranteed zeros
- Check every calculated index. A valid
kdoes not guarantee a validk - 1ork + 1 - Invalid accesses are behavior-classification questions, not numeric-output puzzles
Notes for this lesson
Sign in to keep your progress. Sign in