Course outline

C / Basic: Read and Construct Programs / Lesson 6

Arrays, Strings, and Bounds

Distinguish a sequence’s storage capacity from its logical length, and treat null termination as an explicit invariant.

About 5 min readContent reviewed
On this page

Lesson goals

  • Index arrays using half-open bounds.
  • Explain array-to-pointer conversion and lost size information.
  • Maintain string capacity and terminator requirements.

Capacity and length answer different questions

An array contains a fixed number of elements of one type, stored contiguously. For an array with count elements, valid indices run from zero through count minus one. The logical length may be smaller than the allocated capacity. An empty logical sequence can therefore exist inside a nonempty storage array without requiring any element access.

Use a half-open interval [0, count) when explaining loops. Before every access, show that the index belongs to that interval. C performs no general automatic bounds check. An access one element beyond the array is invalid even if nearby memory appears readable in one experiment.

Pass the length with the data

In most expressions an array converts to a pointer to its first element. A function parameter written int values[] is adjusted to a pointer parameter, so it does not carry the caller’s array length. Pass a separate count and document the readable range. sizeof array / sizeof array[0] computes a count only where array is still an actual array object.

The example has a simple bounded domain: four small nonnegative values. Its sum fits int. A generic summation function would need an overflow policy, and a count of zero must be handled without reading element zero. Keeping these conditions explicit prevents a convenient demonstration from becoming an unjustified general-purpose routine.

c Count and traverse a real array
#include <stddef.h>
#include <stdio.h>

int main(void)
{
    int values[] = {3, 1, 4, 2};
    size_t count = sizeof values / sizeof values[0];
    int total = 0;
    for (size_t i = 0; i < count; ++i) total += values[i];
    printf("count=%zu sum=%d\n", count, total);
    return 0;
}
Expected output
count=4 sum=10

The array contains four int elements and the loop reads each exactly once.

A string needs a reachable terminator

A C string is a sequence of characters ending at the first null character. The terminator occupies storage but is excluded from strlen. char name[] = "Ada" creates four elements, while char name[3] = "Ada" is allowed as an array initializer but leaves no terminator and therefore does not create a C string.

A pointer to character data does not prove that a terminator exists within accessible memory. Calling strlen on unterminated or invalid data can read outside the object. Store a length for arbitrary byte sequences, and reserve string functions for objects whose termination invariant is established. A null pointer and a null character are different concepts.

Construct only when the destination fits

The second example computes the source length, verifies room for that length plus the terminator, and copies exactly that many bytes. The source is a known valid string, so strlen is justified. The check uses length >= capacity to avoid evaluating length plus one in the guard; for these actual arrays, the subsequent copy length is safe.

strncpy is not a universal safe replacement for strcpy: it can omit a terminator and can pad the destination. The appropriate tool depends on the contract. A checked complete copy, an explicit length-based byte operation, or snprintf with its truncation result inspected may each be suitable for a different requirement.

c Copy a complete string after a capacity check
#include <stdio.h>
#include <string.h>

int main(void)
{
    const char source[] = "Ada";
    char destination[8];
    size_t length = strlen(source);
    if (length >= sizeof destination) return 1;
    memcpy(destination, source, length + 1);
    printf("%s (%zu)\n", destination, length);
    return 0;
}
Expected output
Ada (3)

Four bytes are copied, including the terminator; the reported logical length is three.

Text length depends on the representation

strlen counts char units before the terminator, not user-perceived letters or Unicode code points. A UTF-8 word can occupy several bytes per visible character. The baseline language does not make every execution character set UTF-8, so document encoding at input and output boundaries. Embedded null bytes also stop ordinary string functions early.

When using ctype.h functions on a char obtained from byte data, convert it to unsigned char before promotion, unless the value is EOF. Negative plain-char values are outside those functions’ required domain. For binary data, use lengths and memory functions rather than assuming that string and character-classification operations describe the content.

Common errors and fixes

A loop reads the element at index count.

Why it happens
The condition uses <= instead of the half-open upper bound.
How to fix it
Use i < count and review the empty case.
Prevent it next time
State the valid index interval beside the traversal contract.

sizeof inside a function gives the wrong array length.

Why it happens
The parameter is a pointer after adjustment.
How to fix it
Pass count explicitly from the owning scope.
Prevent it next time
Distinguish array objects from pointer expressions in reviews.

A three-letter name causes an out-of-bounds string read.

Why it happens
The three-element buffer contains no terminator.
How to fix it
Reserve one more element and establish termination.
Prevent it next time
Test capacity, logical length, and terminator position independently.

Check your understanding

1. For a destination capacity of four, decide whether "Ada" and "Alan" fit as complete strings. Show the exact comparison.

Show worked solution

"Ada" has length three, so 3 < 4 and copying four bytes including the terminator fits exactly. "Alan" has length four, so 4 >= 4 and must be rejected by the complete-copy contract. Copying only four bytes of "Alan" would not meet that contract because no terminator would remain. Truncation is a different behavior requiring its own explicit policy.

2. Explain how to traverse a logical sequence of zero elements stored in int storage[8], and why reading storage[0] is unnecessary.

Show worked solution

Set count to zero and use for (size_t i = 0; i < count; ++i). The first condition is false, so no element is read. The storage capacity remains eight, but the logical sequence is empty. A reduction such as sum can return its identity value zero; a maximum function instead needs an explicit empty-input failure policy.

Key takeaways

  • Keep capacity, logical length, and string termination separate.
  • Array parameters do not preserve the caller’s array size.
  • Every indexed access needs a valid range; every string operation needs a termination contract.

Official references