Arrays and C-Strings — C-Style Arrays, Pointer Decay, and std::array
In this tutorial, you will learn about Arrays and C. We cover key concepts, practical examples, and best practices to help you master this topic.
C++ supports both C-style arrays inherited from C and the modern std::array container, with C-strings being null-terminated character arrays prone to buffer overflow if mishandled.
What You'll Learn
You will declare and initialize C-style arrays, understand array-to-pointer decay and why it causes bugs, work with C-strings and the cstring library, use std::array as a safer fixed-size container, pass arrays to functions correctly, and avoid common buffer overflow and off-by-one errors.
Why It Matters
Arrays are the most fundamental data structure in computing: contiguous memory of identical elements. C-style arrays are everywhere in legacy code, operating system APIs, and Embedded Systems. Understanding them deeply, and knowing when to prefer std::array or std::vector, separates intermediate C++ programmers from beginners.
Learning Path
graph LR
A["08: Loops"] --> B["09: Arrays & C-Strings"]
B --> C["10: Functions"]
C --> D["11: Classes & Objects"]
style A fill:#4a90d9,stroke:#2c5f8a,color:#fff
style B fill:#4a90d9,stroke:#2c5f8a,color:#fff
style C fill:#4a90d9,stroke:#2c5f8a,color:#fff
style D fill:#4a90d9,stroke:#2c5f8a,color:#fff
C-Style Arrays
#include <iostream>
int main() {
// Declaration and initialization
int arr[5]; // uninitialized: contains garbage
int arr2[5] = {1, 2, 3, 4, 5}; // fully initialized
int arr3[5] = {1, 2}; // {1, 2, 0, 0, 0} (rest zero)
int arr4[] = {1, 2, 3}; // size deduced: 3 elements
// Access via indexing (zero-based)
std::cout << arr2[0] << "\n"; // 1
std::cout << arr2[4] << "\n"; // 5
// Size computation
int size = sizeof(arr2) / sizeof(arr2[0]); // 5
std::cout << size << "\n";
// Iteration
for (int i = 0; i < 5; ++i) {
arr2[i] *= 2;
}
for (int x : arr2) {
std::cout << x << " ";
}
std::cout << "\n";
// Output: 2 4 6 8 10
}
Arrays have a fixed size determined at compile time. You cannot resize a C-style array.
Array-to-Pointer Decay
#include <iostream>
void printSize(int arr[]) {
std::cout << sizeof(arr) << "\n"; // prints 8 (pointer size on 64-bit)
}
int main() {
int arr[10];
std::cout << sizeof(arr) << "\n"; // prints 40 (10 * 4)
printSize(arr); // prints 8 (arr decays to pointer)
// The decay means you cannot use range-based for on function arguments
int* ptr = arr; // implicit decay
std::cout << *(ptr + 3) << "\n"; // same as arr[3]
}
When you pass a C-style array to a function, it decays to a pointer to the first element. The function receives no size information. You must pass the size separately.
Multidimensional Arrays
#include <iostream>
int main() {
// 2D array: 3 rows, 4 columns
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
for (int r = 0; r < 3; ++r) {
for (int c = 0; c < 4; ++c) {
std::cout << matrix[r][c] << " ";
}
std::cout << "\n";
}
// Only the first dimension can be omitted (size deduced)
int mat[][3] = {
{1, 2, 3},
{4, 5, 6}
};
}
Multidimensional arrays are stored in row-major order: all elements of row 0, then all elements of row 1, and so on. This matters for cache performance.
C-Strings
C-strings are arrays of char terminated by a null character ('\0', ASCII 0).
#include <iostream>
#include <cstring>
int main() {
// String literal (null-terminated automatically)
const char* greeting = "Hello";
// Memory layout: {'H', 'e', 'l', 'l', 'o', '\0'}
// Character array with explicit null
char name[6] = {'A', 'l', 'i', 'c', 'e', '\0'};
std::cout << name << "\n";
// String literal initializer
char city[] = "Boston"; // size is 7 (includes null)
std::cout << city << "\n";
// cstring functions
char buffer[50];
std::strcpy(buffer, "Copy ");
std::strcat(buffer, "this ");
std::strcat(buffer, "string");
std::cout << buffer << "\n";
std::cout << "Length: " << std::strlen(buffer) << "\n";
// Comparison
if (std::strcmp(greeting, "Hello") == 0) {
std::cout << "Strings match\n";
}
}
C-string functions (strcpy, strcat, strcmp) do not check buffer sizes. They are a major source of security vulnerabilities. Prefer std::string in C++.
std::array — Fixed-Size Array (C++11)
std::array wraps C-style arrays in a class that knows its size and provides STL container interface.
#include <iostream>
#include <array>
#include <algorithm>
int main() {
std::array<int, 5> arr = {1, 2, 3, 4, 5};
// Knows its size
std::cout << arr.size() << "\n"; // 5
// STL algorithms
std::reverse(arr.begin(), arr.end());
for (int x : arr) {
std::cout << x << " ";
}
std::cout << "\n";
// Output: 5 4 3 2 1
// Bounds-checked access (throws std::out_of_range)
try {
std::cout << arr.at(10) << "\n";
} catch (const std::out_of_range& e) {
std::cout << "Out of range: " << e.what() << "\n";
}
// Unchecked access (faster, like C-style)
std::cout << arr[2] << "\n"; // 3
// Fill with value
arr.fill(0);
std::cout << arr[0] << "\n"; // 0
// No decay: passes size information
auto printArr = [](const auto& a) {
std::cout << "Size: " << a.size() << "\n";
};
printArr(arr);
}
std::array has zero overhead compared to C-style arrays. It stores elements directly (no heap allocation) and the compiler can inline all operations.
Comparison: C-Array vs std::array vs std::vector
| Feature | C-Array | std::array | std::vector |
|---|---|---|---|
| Size known at compile time | Yes | Yes | No |
| Knows its own size | No | Yes | Yes |
| STL algorithm support | No | Yes | Yes |
| Dynamic resizing | No | No | Yes |
| Heap allocation | No | No | Yes |
| Pass to function safely | Pass size too | Yes | Yes |
Common Mistakes
Mistake 1: Off-by-One (Buffer Overflow)
int arr[5];
arr[5] = 42; // writes beyond array, undefined behavior
C++ does not check array bounds. Accessing beyond the end of an array can corrupt memory or crash your program.
Mistake 2: Array Decay in Function Parameters
void process(int arr[10]) {
// arr is actually int*, size argument is ignored
for (int i = 0; i < 10; ++i) { ... } // time bomb
}
The 10 in the parameter is ignored by the compiler. Pass the size as a separate parameter or use std::array.
Mistake 3: Forgetting Null Terminator
char buf[3] = {'a', 'b', 'c'}; // no null terminator!
std::cout << buf; // undefined behavior: reads past end
Always leave room for the null terminator.
Mistake 4: Using strcpy Without Size Check
char dest[10];
strcpy(dest, "This is a very long string"); // buffer overflow
Use strncpy or, better yet, std::string.
Mistake 5: Confusing Array of Pointers with Pointer to Array
int* arr1[5]; // array of 5 pointers to int
int (*arr2)[5]; // pointer to array of 5 ints
Mistake 6: Returning Pointer to Local Array
int* getArray() {
int arr[5] = {1, 2, 3, 4, 5};
return arr; // arr is destroyed when function returns
}
Return std::array<int, 5> or std::vector<int> instead.
Practice Questions
- What is array-to-pointer decay and why does it happen?
- Write code that reverses a C-style array in place without using
std::reverse. - What is the difference between
char s[] = "hello"andconst char* s = "hello"? - Convert a C-style array program to use
std::array. Compare the code. - What is
strlenguaranteed to return? How does it determine the length?
Challenge
Write a function template that accepts a std::array of any size and any type, finds the maximum element, and returns it. Test with std::array<int, 5> and std::array<double, 3>.
FAQ
Mini Project
Write a word reversal program using C-strings:
#include <iostream>
#include <cstring>
void reverse(char str[]) {
int len = std::strlen(str);
for (int i = 0; i < len / 2; ++i) {
char temp = str[i];
str[i] = str[len - 1 - i];
str[len - 1 - i] = temp;
}
}
int main() {
char text[100];
std::cout << "Enter a string: ";
std::cin.getline(text, 100);
reverse(text);
std::cout << "Reversed: " << text << "\n";
// Now with std::array and std::reverse
std::array<char, 100> arr = {};
std::cout << "Enter another string: ";
std::cin.getline(arr.data(), arr.size());
size_t len = std::strlen(arr.data());
std::reverse(arr.begin(), arr.begin() + len);
std::cout << "Reversed (std::array): " << arr.data() << "\n";
}
What's Next
Arrays store sequences of data. The next lesson covers functions: pass by value, pass by reference, pass by address, function overloading, and default arguments. You will learn how to organize code into reusable blocks.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro