Arrays, strings & C-style vs std::string
Scenario
You try to concatenate two strings using the '+' operator. It works perfectly for std::string, but when you try it with "Hello" + "World", it fails to compile.
Why doesn't C++ treat string literals the same way it treats std::string?
Mental model
A C-style string is just a raw sequence of characters in memory that ends when it hits a 0. It's like reading until you hit a blank page. A std::string is a smart book that knows exactly how many pages it has and can dynamically bind more pages if needed.
C++ inherited C-style strings, which are simply arrays of characters ending with a null-terminator (`'\0'`). They have no bounds checking, don't know their own size, and cannot be compared or concatenated with normal operators. `std::string` is a modern C++ class that manages its own memory, knows its size in O(1) time, and supports intuitive operators like `+` and `==`.
Explanation
In C, strings are represented as arrays of `char`. Because arrays degrade to pointers when passed to functions, a C-style string loses its size information. The only way to know where it ends is the null-terminator (`'\0'`). Finding the length takes O(N) time (`strlen`). You cannot concatenate them with `+`; you must use `strcat` and manually manage buffer sizes, which is a massive source of buffer overflow vulnerabilities.
`std::string` is a C++ Standard Library class that acts as a wrapper around character data. It stores the length explicitly (O(1) `.length()`), automatically handles memory allocation/resizing, and overloads operators so `a == b` compares the contents (unlike C-strings, where `a == b` compares memory addresses).
String literals like `"Hello"` are statically allocated C-style arrays (`const char[6]`). To force a literal to be a `std::string`, use the `s` suffix (C++14): `"Hello"s`. You can also implicitly convert a C-string to a `std::string` by assignment.
Code examples
The nightmare of C-style strings
#include <cstring>
#include <iostream>
int main() {
const char* a = "Hello";
const char* b = "Hello";
// WRONG: Compares pointer addresses, not string contents!
if (a == b) { }
// CORRECT but tedious:
if (std::strcmp(a, b) == 0) { }
char buffer[20] = "Hello ";
// Manual length checking needed to prevent buffer overflows
std::strcat(buffer, "World");
return 0;
}
C-style strings are raw pointers. Operator `==` compares the addresses in memory. To compare contents, you must use `strcmp`. Modifying them requires manual buffer sizing.
The simplicity of std::string
#include <string>
#include <iostream>
int main() {
std::string a = "Hello";
std::string b = "Hello";
// Works intuitively: compares contents.
if (a == b) {
std::cout << "Equal\n";
}
// Dynamically resizes, no buffer overflow risk.
std::string c = a + " World";
// O(1) length lookup
std::cout << "Length: " << c.length() << '\n';
return 0;
}
`std::string` overloads operators to behave like primitive types. It abstracts away the memory management entirely.
Key points
- Literals are C-style: String literals in quotes (`"text"`) are C-strings under the hood.
- Default to std::string: Never use C-strings in modern C++ unless interfacing with legacy C APIs.
Common mistakes
- Concatenating two string literals: Writing `"Hello" + " World"` tries to add two `const char*` pointers together, which is illegal. At least one operand must be a `std::string` (e.g., `std::string("Hello") + " World"`).
Recall questions
- How does a C-style string indicate where it ends?
- What happens if you use the == operator on two C-style strings?
- Why is std::string.length() an O(1) operation, while strlen() on a C-string is O(N)?
Questions & answers
If you need to return a substring from a function, why should you return a `std::string` instead of a `char*`?
Returning a `char*` usually means either returning a pointer to a local buffer (which goes out of scope and causes undefined behaviour) or returning dynamically allocated memory (forcing the caller to manually `delete[]` it). `std::string` handles its own memory lifecycle automatically via RAII.
Approach: Connect string management to memory lifecycle and dangling pointers.
A candidate uses `sizeof(str)` on a C-style string `char str[] = "Hello";` to find its length. What is wrong with this?
`sizeof(str)` returns the total allocated size of the array, which includes the null-terminator (so it returns 6, not 5). Furthermore, if `str` decays to a pointer, `sizeof(str)` returns the size of the pointer (usually 8 bytes), not the string length. `strlen()` or `std::string` should be used instead.
Approach: Understand the difference between array sizes and string lengths, and the decay to pointer.
Continue learning
Previous: Functions — pass by value, reference & pointer