Runtime errors and undefined behavior

C++ "vector subscript out of range": causes and fixes

A vector subscript is out of range when code indexes an element that does not exist. A vector of size n has valid indices from zero through n - 1, so using n itself is invalid. Unchecked access in builds without runtime checks has undefined behavior.

Check the valid range

std::vector<T>::size() counts existing elements. The subscript operator operator[] expects an index smaller than that count. It does not grow the vector and it does not check the index in the C++ standard. If a vector contains three values, only positions zero, one, and two exist. Position three is one past the end.

Some standard library debug modes add checks and report a runtime assertion such as a vector subscript being out of range. The wording depends on library and configuration. That diagnostic helps locate an invalid access, but the underlying logic still needs a bounds check. In a release configuration without checks, the same invalid access does not reliably produce a message; it has undefined behavior.

MSVC "Debug Assertion Failed" dialog

In an MSVC debug build, the debug runtime shows a Debug Assertion Failed! dialog. Its Expression: line says vector subscript out of range. This dialog is a debug-time check, not normal fixture output. A release build may not perform this check.

Fix loop boundaries

An off-by-one error often comes from using i <= values.size() as a loop condition. That condition permits i == values.size(), which is one past the last valid index. Use i < values.size() so the loop stops before that position. A std::size_t value is unsigned, so decrementing zero wraps to a large value instead of becoming negative.

Here is deliberately unsafe code. Do not run it or rely on a particular output. The vector contains three elements, but the loop visits index three as well.

#include <cstddef>
#include <vector>

void inspect(const std::vector<int>& values) {
    for (std::size_t i = 0; i <= values.size(); ++i) {
        (void)values[i];
    }
}

int main() {
    const std::vector<int> measurements{14, 27, 41};
    inspect(measurements);
    return 0;
}

This example compiles but has undefined behavior when the loop reaches the invalid index. There is no valid expected output. The MSVC debug runtime behavior is described below; it is not fixture output.

Use an indexed loop with i < values.size() to visit the existing elements. A range-based for is another good choice when you do not need the index.

#include <cstddef>
#include <iostream>
#include <vector>

int main() {
    std::vector<int> measurements{14, 27, 41};
    for (std::size_t i = 0; i < measurements.size(); ++i) {
        std::cout << measurements[i] << '\n';
    }
    return 0;
}

Expected output:

14
27
41

Validate empty and user-selected indices

An empty vector has size zero, so it has no valid index. Check whether a value exists before accessing the first element with values[0] or values.front(). A call to push_back adds an element. Calling reserve() can increase a vector’s capacity without adding elements or changing its size. In contrast, constructing std::vector<int> values(5) creates five elements. Capacity describes reserved storage, while size describes the number of objects that can be accessed.

When an index comes from input, check both that the input succeeded and that the index is less than the current size. Reject negative values before converting them to an unsigned index. A negative signed integer converted to std::size_t becomes a large positive value and will not represent a valid position.

Choose checked access

at(index) checks its argument and throws std::out_of_range when it is not valid. Use it when an exception is an appropriate way for your program to report invalid indexing. A try-catch block can handle the error at a useful boundary. The check does not replace validating user input when a friendly prompt or recovery path is expected.

A range-based for loop avoids writing an index when you need every element. Use an indexed loop when the position matters or when you compare neighboring elements. If you need a neighboring item, test that the required next index exists before reading it. For reverse iteration, use an iterator pattern or a carefully designed loop that cannot decrement past the beginning.

When debugging, inspect the vector's size at the exact access, not only where it was first created. Earlier conditions may skip an append, input parsing may fail, or another operation may erase an element. Follow the index value and size together. A breakpoint immediately before the access can show whether the index is negative, equal to size, or much larger.

Keep the check next to the access when possible. Distant checks are easy to invalidate after the vector changes. If a helper function accepts an index, document or enforce the valid range at that boundary. Do not use capacity() as a substitute for size(); reserved slots are not vector elements.

Read the libstdc++ .at() range-check message

std::vector::at() throws std::out_of_range when an index is invalid. If the program does not catch the exception, GCC's libstdc++ prints a termination line and a what() line before the process exits abnormally. In this example, __n is the requested index, five, and this->size() is the vector size, also five. The comparison shows that index five is equal to the size and therefore past the last valid index.

#include <vector>

int main() {
    const std::vector<int> values{10, 20, 30, 40, 50};
    (void)values.at(5);
    return 0;
}

GCC runtime diagnostic:

terminate called after throwing an instance of 'std::out_of_range'
  what():  vector::_M_range_check: __n (which is 5) >= this->size() (which is 5)

The exact wording varies by compiler and version. The bounds check prevents an unchecked subscript, but an uncaught exception still terminates the process. Catch it or validate the index before calling at().

Exercise

Read an index from the user and print the matching word only when the index is valid. Otherwise print Index outside vector. Sample input:

3

Expected output:

Index outside vector

Hint 1: The sample vector has three elements. Hint 2: Reject an index greater than or equal to size().

Show one possible solution
#include <iostream>
#include <string>
#include <cstddef>
#include <vector>

void printAt(const std::vector<std::string>& words, std::size_t index) {
    std::cout << words.at(index) << '\n';
}

int main() {
    const std::vector<std::string> words{"red", "green", "blue"};
    int signedIndex = 0;
    if (!(std::cin >> signedIndex)) {
        return 1;
    }
    if (signedIndex < 0 || static_cast<std::size_t>(signedIndex) >= words.size()) {
        std::cout << "Index outside vector\n";
    } else {
        printAt(words, static_cast<std::size_t>(signedIndex));
    }
    return 0;
}

Keep learning with CppPath

Continue with the std::vector Basics lesson. The full lesson includes a quiz and a checked coding challenge with a free account.