Pointers and Arrays in C-Style Code
An array is less flexible than a vector because it is simpler, and it can offer a real performance advantage by accessing stack-allocated elements directly rather than through a handle to the free store.
A Question Worth Asking About Containers
When a programmer first meets array alongside vector, a natural question follows. If vector is so much more flexible, why would anyone bother with an array at all? And when the language already provides built-in arrays, why add another option? These questions are reasonable, and the answers reveal a set of trade-offs rather than a single winner. This article starts from the beliefs that tend to mislead people here, then works through what is actually true.
What do you think happens?
Before reading on: when your program reads one element of a vector, do you think it needs one memory step or more than one? What about reading one element of an array?
Reveal answer
A vector reaches its elements indirectly, through the vector acting as a handle to memory on the free store, so reading an element takes an extra step compared with an array whose elements sit directly on the stack.
The source describes a vector as keeping its elements on the free store and being reached through a handle, while an array can place its elements directly on the stack. The extra indirection is where the difference in work comes from.
Notice what the question actually asks. Why use an array when vector is more flexible? The answer given is that an array is less flexible, and that is exactly its purpose. It is simpler. Simplicity is not just a matter of tidiness. It changes how the elements are reached in memory, and that change can matter for speed.
Where the Elements Live
Consider the choice in plain terms. A vector keeps its elements on the free store, which is the memory pool used for dynamic allocation. The program reaches those elements indirectly, through the vector acting as a handle. An array can instead place its elements directly on the stack. Each approach asks the program to do a different amount of work before it can read a value.
The diagram shows the two paths. Reading an array element that lives directly on the stack needs no intermediate hop. Reading a vector element goes through the handle first. That saved hop is where the performance advantage of a simpler container comes from. The source also warns that the stack is a limited resource, so the speed of an array has to be weighed against the risk of stack overflow. Placing elements on the stack is not free; it spends a finite resource.
How Subscripting Reaches an Element
Subscripting is how a program asks for one element by position. Written as arr[i], it behaves as if the program dereferenced arr plus i, meaning it starts at the location of the first element and moves forward by i element-sized steps. The step size is the size of the type the pointer refers to. That is why the type of a pointer matters: it decides how far each step travels.
The diagram captures the idea. Each index moves the reading position forward by whole element-sized steps from the start. As long as the pointer's type matches the real elements, every step lands exactly on the start of the next element. The arithmetic depends entirely on that match.
In this expression, i is the index and s is the size, in bytes, of the type the pointer refers to. The offset is how far from the first element the read lands. If s matches the size of the real elements, the offset is always a whole number of elements. If s is smaller, the offset can fall partway through an element.
What Decay Throws Away
A built-in array does not carry its size along with it the way an array object does. The information about how many elements exist must be tracked separately, which is a common source of errors. The more serious issue is decay. Decay is the conversion in which a built-in array's name is treated as a pointer to its first element. For most purposes this conversion is harmless, and it is how built-in arrays are passed around in older C-style code. Once decay happens, the size the program relied on is no longer attached to the value it is working with.
A built-in array of Circle objects, with its size known at the point of declaration.
The program can see the array and reason about how many elements it holds.
The same array passed as a pointer to its first element, with the element count no longer attached.
The value in use is only a pointer. Its size information is gone, and the pointer's type alone decides how offsets are computed.
Subscripting Through a Pointer of the Wrong Type
Suppose a derived type Circle is larger than its base type Shape, and an array of Circles is accessed through a pointer declared to point to Shape. The source assumes the size of Shape is less than the size of Circle. What goes wrong when the program subscripts through that pointer?
- 1Identify the element size the pointer uses
The pointer is of type Shape, so each step through it covers the size of a Shape, not the size of a Circle.
- 2Compare that step with a real element
Because Shape is smaller than Circle, one step covers less distance than one whole Circle.
- 3Compute where index 1 lands
Index 1 should point at the start of the second Circle. Instead, the step ends partway through the first Circle, because it advanced by a Shape-sized amount.
- 4State the consequence
The computed location lies inside the wrong element. Reading or writing there produces a value that does not correspond to any intended Circle.
Subscripting through a pointer of a different type can compute the wrong offset, landing inside the wrong element. The array's size information was lost at decay, and nothing in the pointer's type alone guards against this.
Choosing Among Array, Vector, and Built-In Array
The second comparison is between array and the built-in array the language provides directly. The source gives three reasons an array is preferred. First, an array knows its size, which makes it straightforward to use with standard-library algorithms that need to know where a sequence ends. Second, an array can be copied using assignment or initialization. Third, and the source calls this its main reason, an array saves the programmer from surprising and nasty conversions to pointers.
| Property | Built-in array | array | vector |
|---|---|---|---|
| Carries its own size | No, tracked separately | Yes | Not discussed in the source |
| Can silently decay to an unrelated pointer type | Yes | No | No, standard containers do not offer this |
| Element access path | Direct | Direct, from stack-allocated elements | Indirect, through a handle to the free store |
| Flexibility | Not discussed in the source | Less flexible, by design | More flexible |
| Simplicity | Simple | Simpler than vector | More involved |
Trade-offs drawn from the source. Where the source is silent, the cell says so rather than guessing.
The three columns show the trade-off rather than a winner. A vector gives flexibility and pays for it with an indirect path to its elements. An array gives up flexibility to keep the sequence simple and direct. A built-in array sits closest to the language and carries the most risk of losing its size at decay. The source's advice follows from this: when a fixed-size sequence is needed and the sequence should keep its identity, array is the safer choice over a built-in array that will be passed around as a pointer.
Mistakes That Undo the Benefit
Common mistake Assuming an array is always the faster choice because it is simpler
What it looks likeChoosing an array for every sequence in a program, including ones that must change size over time.
Why it failsThe performance advantage comes from direct stack access, but the stack is a limited resource, and the simplicity that helps speed also makes the array less flexible.
Do this insteadWeigh the speed benefit against both the stack limit and the need for flexibility before choosing.
Common mistake Passing a built-in array around as a plain pointer and forgetting its size
What it looks likeA function receives a pointer to the first element and loops over it without any count being passed alongside.
Why it failsDecay removes the size from the value in use, so the function has no reliable way to know where the sequence ends.
Do this insteadUse an array object, which knows its size, or track the element count explicitly alongside the pointer.
Common mistake Subscripting through a pointer whose type does not match the real elements
What it looks likeIndexing an array of derived objects through a pointer typed to the base class.
Why it failsThe pointer's type sets the step size, so a smaller base-type step lands partway through an element rather than at the next one.
Do this insteadMake sure the pointer's type matches the element type actually stored.
Applying the Choice to a Task
A program needs a fixed set of exactly four sensor readings that are passed to several helper functions. Which container should it use, and why? Justify your choice in terms of size handling, the risk of decay, and the flexibility each option gives. Then say what would change if the number of readings had to grow at runtime.
Hints
- Ask which option keeps the size attached to the value when it is passed to a function.
- Consider whether the program needs to change the number of elements after it starts running.
- Recall that the source identifies an array's main advantage as avoiding surprising conversions to pointers.
Summary of the Trade-Offs
An array is less flexible than a vector because it is simpler, and that simplicity can yield a real performance advantage. Its elements can sit directly on the stack, so reading them avoids the handle-based step a vector requires, though the stack is a limited resource. A built-in array loses its size information when it decays to a pointer, and subscripting through a pointer of a mismatched type can compute an offset that lands inside the wrong element. Among the choices, array is preferred over a built-in array for its size awareness, copyability, and freedom from unsafe pointer conversions, while vector remains the choice when flexibility matters most.
At a glance
- An array trades flexibility for simplicity, and its direct access to stack-allocated elements can offer a performance advantage over a vector's handle to the free store.
- The stack is a limited resource, so the speed of an array must be weighed against the risk of stack overflow.
- A built-in array can decay to a pointer, losing its size, and subscripting through a pointer of a different type can compute the wrong offset.
- Array is preferred over a built-in array because it knows its size, can be copied by assignment or initialization, and avoids surprising pointer conversions.
- Choose the container by matching the need: vector for flexibility, array for a simple fixed-size sequence that keeps its identity.