共计 2320 个字符,预计需要花费 6 分钟才能阅读完成。
sizeof 陷阱:为什么我的数组大小总不对?
刚学 C ++ 时,我写过这样的代码:

void printSize(int arr[]) {std::cout << "Array size:" << sizeof(arr) / sizeof(arr[0]) << std::endl;
}
int main() {int nums[] = {1,2,3,4,5};
std::cout << "Real size:" << sizeof(nums) / sizeof(nums[0]) << std::endl; // 正确输出 5
printSize(nums); // 输出 2(32 位系统)或 1(64 位系统)}
这个经典陷阱揭示了 C ++ 数组传参的核心问题: 数组会退化为指针 。当数组作为函数参数时,编译器实际上传递的是指向数组首元素的指针,所有数组长度信息都丢失了。
传统传参方式三分天下
1. 指针模式(int* arr)
void process(int* arr, size_t len) {for(size_t i=0; i<len; ++i)
arr[i] *= 2;
}
- 优点:最灵活,兼容 C 语言
- 缺点:
- 必须手动传递长度
- 无法防止越界访问
- 类型信息不完整
2. 定长数组模式(int arr[5])
void process(int arr[5]) {/* 实际仍会退化为指针 */}
- 优点:语法直观
- 缺点:
- 长度只是文档提示,实际仍会退化为指针
- 长度硬编码不灵活
3. 数组引用模式(int (&arr)[N])
template<size_t N>
void process(int (&arr)[N]) {static_assert(N > 0, "Array cannot be empty");
// 现在可以安全使用 N
}
- 优点:保留数组长度信息
- 缺点:
- 语法晦涩
- 需要模板推导
- 仅适用于栈数组
现代 C ++ 解决方案
std::array:编译期已知长度的首选
#include <array>
void process(std::array<int, 5>& arr) {for(auto& num : arr)
num *= 2;
}
// 使用示例
std::array<int, 5> nums{1,2,3,4,5};
process(nums);
- 优势:
- 完整保留长度信息
- 支持迭代器
- 值语义(可整体拷贝)
- 限制:长度必须编译期确定
std::span:运行时长度的安全视图(C++20)
#include <span>
void process(std::span<int> arr) {if(arr.empty()) return;
for(auto& num : arr)
num *= 2;
}
// 支持各种容器
std::vector<int> vec{1,2,3};
int raw[] = {4,5,6};
process(vec);
process(raw);
- 优势:
- 统一处理各种连续内存(原生数组 /vector/string 等)
- 自动携带长度信息
- 零开销抽象
防御性编程实战
编译期检查(static_assert)
template<typename T>
void safeProcess(T& container) {
static_assert(
std::is_same_v<T, std::array<int,5>> ||
std::is_same_v<T, std::vector<int>>,
"Only support specific containers"
);
// ...
}
运行时边界检查
void safeAccess(std::span<int> arr, size_t idx) {if(idx >= arr.size()) {throw std::out_of_range("Index out of bounds");
}
arr[idx] = 42;
}
性能与安全权衡
| 方式 | 长度信息 | 边界检查 | 拷贝成本 | 适用场景 |
|---|---|---|---|---|
| 原生数组 | ❌ | ❌ | 低 | 性能敏感代码 |
| std::array | ✔️ | 可选 | 中 | 固定长度容器 |
| std::span | ✔️ | 可选 | 无 | 只读视图 / 接口参数 |
| std::vector | ✔️ | ✔️ | 高 | 动态数组 |
避坑指南
多线程安全
- 只读访问:多个线程可同时读取同一数组
- 写操作:必须同步(mutex/atomic)
- 警惕 false sharing:对高频修改的相邻数据做 padding
struct ThreadSafeArray {
std::array<int, 100> data;
std::mutex mtx;
void safeWrite(size_t idx, int val) {std::lock_guard<std::mutex> lock(mtx);
data.at(idx) = val; // at() 自带边界检查}
};
迭代器失效
std::vector<int> vec{1,2,3};
auto it = vec.begin();
vec.push_back(4); // 可能导致迭代器失效
// 正确做法:操作后重新获取迭代器
思考题:动态数组包装类
尝试设计一个 SafeArray 类,要求:
1. 支持运行时动态大小
2. 自动边界检查
3. 提供迭代器接口
4. 线程安全的随机访问
提示:可以组合使用 std::vector 和 std::mutex 实现。
单元测试示例
#define CATCH_CONFIG_MAIN
#include <catch2/catch.hpp>
#include "array_utils.h"
TEST_CASE("Array processing") {SECTION("std::array sum") {std::array<int,3> arr{1,2,3};
REQUIRE(arraySum(arr) == 6);
}
SECTION("Boundary check") {std::vector<int> vec(10);
REQUIRE_THROWS_AS(safeAccess(vec, 15), std::out_of_range);
}
}
正文完
