C++ / a working model

02 / 163   ·   C++11   ·   7 min

sizeof and strlen: Capacity, Terminator, and Logical Length

Keep this sentence

sizeof measures type occupancy; strlen walks a character sequence looking for the first zero character. They answer different questions. A string literal includes a terminating zero; an embedded zero ends strlen early. A pointer itself records no capacity and does not guarantee that it points to a valid string.

In this lesson
  1. Do not conflate three different lengths
  2. Embedded zeros and pointers
  3. Let the interface express the real data model
  4. Example
  5. Exercise

Do not conflate three different lengths

For char text[20] = "cat", the array capacity is twenty chars, the current C-string length is three characters, and storing that string requires at least four chars because of the trailing zero. sizeof(text) returns 20; std::strlen(text) returns 3 and does not include the terminator.

sizeof obtains size from the expression type and does not scan contents. strlen's semantics require finding the first zero character; that usually needs a scan proportional to the prefix length, although a compiler may fold a call on a known literal into a constant. Optimization does not turn strlen into a tool that can query the capacity of an arbitrary buffer.

Keep capacity, terminator-inclusive storage, and logical C-string length as three separate numbers. Mixing them is how buffer sizes and string lengths get swapped in interfaces. sizeof never walks memory looking for a zero; strlen never reports how large the array or allocation is.

Embedded zeros and pointers

The literal "ab\0cd" contains five explicit characters plus an automatically appended terminator, six chars in total. strlen stops at the middle zero and returns only 2; the later c and d still exist in the array and have not been deleted.

After assigning the array to a const char*, sizeof measures the pointer, no longer six elements. strlen still scans from the pointed-to location, so the two expressions are neither equivalent nor interchangeable. UTF-8 text further requires distinguishing encoded byte count from the number of characters a user sees; strlen does not understand Unicode character boundaries.

An embedded zero is data, not a request to discard the rest of the array. Once you hold only a pointer, sizeof has already lost the array bound, while strlen still depends on a reachable terminator and on the encoding being a simple zero-terminated byte string.

Let the interface express the real data model

When data is guaranteed to be zero-terminated, a C-string interface may use strlen. For network frames, compressed data, and text fragments that contain zero bytes, pass an explicit length. The example uses std::string(raw, sizeof(raw) - 1) to keep the embedded zero and exclude only the final zero the literal added automatically.

A string's size is the number of char elements it holds and needs no terminator scan. C++17's string_view can also store a length, but it does not own the data and does not promise a zero at the end of the view. Passing view.data() directly to an interface that requires a C string may read past the view's bounds.

Choose the length model that matches the data. Zero-terminated text can use strlen; binary or interior-zero text must carry a count. Owning string objects and non-owning views both store size explicitly, which is the property C-string APIs do not have unless you add it.

Pitfalls

  • Calling strlen on a character array with no reachable terminator reads out of bounds and is undefined behavior; a result that happens to look correct does not prove safety.
  • strlen(nullptr) is illegal; sizeof(pointer) is legal but also does not prove that the pointer is dereferenceable.

Run an example

Minimum C++11 · complete program · Download .cpp

#include <cassert>
#include <cstring>
#include <iostream>
#include <string>

int main() {
    const char raw[] = "ab\0cd";
    const std::string text(raw, sizeof(raw) - 1);
    assert(sizeof(raw) == 6);
    assert(std::strlen(raw) == 2);
    assert(text.size() == 5);
    assert(text[3] == 'c');
    std::cout << sizeof(raw) << ' ' << std::strlen(raw)
              << ' ' << text.size() << '\n';
}

Compile locally

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread basics-sizeof-strlen.cpp -o example && ./example

Expected result

6 2 5

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

For char buffer[8] = {'o', 'k', '\0', 'x'};, what are sizeof and strlen respectively? How do you construct a string that contains the first four elements?

Show a reference answer

sizeof(buffer) is 8; strlen(buffer) is 2, because the third element is already a zero character. Use std::string s(buffer, 4) to obtain a string of size 4, where s[2] is a zero character and s[3] is x. Writing only std::string s(buffer) follows the terminator convention and stores only ok.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog