C++中老式C风格void*的优雅与简洁

Hacker News Top 新闻

摘要

文章讨论了在C++中使用C风格void*指针与uint8_t*和std::span传递内存块的优缺点,主张void*的简洁性和可读性。

暂无内容
查看原文
查看缓存全文

缓存时间: 2026/06/09 11:41

# 如何声明一个接受内存块(blob of memory)的 C++ 函数? 来源:https://giodicanio.com/2026/06/05/how-to-declare-a-c-plus-plus-function-that-takes-a-blob-of-memory/ 讨论几种选项,从古老的 C 风格 `void*` 指针开始。 在 C++ 中你可能会问一个有趣的问题:“如何声明一个接受内存块(blob of memory)作为输入的函数?” 举个例子,考虑一个对输入数据进行哈希(比如 SHA-256 或其他算法)的函数,或者一个接收二进制数据并将其写入磁盘的函数。 从我 C 语言的背景出发,我首先想到的选项当然是: ```cpp void DoSomething(const void* p, size_t numBytes) ``` 你只需要传递一个指向输入内存块起始位置的 **const void*** 指针,以及该内存块的总大小(以字节为单位)。 然后,可能会有一些 C++ 程序员抱怨:“嘿,你怎么还在用那个 **不安全** 的旧 C 风格 `void*` 指针?用像 `uint8_t` 这样安全的显式类型吧,它明确表示一个 8 位的字节!” 于是他们提议“升级”到以下原型: ```cpp void DoSomething(const uint8_t* p, size_t numBytes) ``` 现在,假设你想向这个函数传递一个自定义结构体,比如: ```cpp struct MyCustomData { ... }; MyCustomData data; ``` 使用原始的 `void*` 版本,你可以 **简单明了地** 调用函数: ```cpp DoSomething(&data, sizeof(data)); ``` 代码非常清晰直接:你传递了一个指向自定义数据结构的指针,以及它的字节大小。就是这样,简单明了。 另一方面,对于“安全现代”的 `uint8_t` 原型,函数调用变得更复杂了,因为你需要添加一个类型转换: ```cpp // void DoSomething(const uint8_t* p, size_t numBytes) // // DoSomething(&data, sizeof(data)); // // 当函数期望 const uint8_t* 而不是 const void* 时,这会导致编译器错误,类似: // // 错误:无法将 'MyCustomData*' 转换为 'const uint8_t*' // // 这时你需要显式强制转换! DoSomething( reinterpret_cast<const uint8_t*>(&data), sizeof(data) ); ``` 为什么人们要用 `uint8_t` 指针(或 `std::byte`)来 **复杂化和丑化** 他们的 C++ 代码呢?`void*` 明明工作得很好! 此外,有些人可能会说:“嘿,在现代 C++20 中,我们有 **std::span**!用它!” 好吧,恭喜你,这进一步增加了代码的复杂度和噪音!实际上,**std::span** 是一个 **类模板**,而有些人会建议让处理通用内存块的函数变成一个 **函数模板**!真的吗?像这样? ```cpp template <typename T> void DoSomething(std::span<T> data) ``` 或者更复杂的东西,比如这样? ```cpp template <typename T> void DoSomething(std::span<const T> data) // 或者这样? template <typename T> void DoSomething(std::span<const std::byte> data) ``` 哇。用 `std::span`,复杂度指针直接爆表,还更高了! 有人可能建议用 **std::span**?但这也比最初的 `void*` 签名更复杂。你想要一个指向通用内存块的指针吗?C++ 从一开始就从 C 继承了:它叫 `void*`!用它,享受它吧。 我非常不喜欢某些“现代”C++ 程序员的态度,他们做出的选择实际上让代码 **更复杂**、更丑陋、更难以编写和理解。似乎有些人已经失去了对 **良好可读代码** 的品味。来自 C 的一些好习惯仍然可以 **积极地** 用在 C++ 中,比如 `void*` 指针和大小参数。 --- 另外,作为一个不错的补充,如果你使用 **SAL 注解**,函数可以稍微装饰一下,以帮助代码分析器检测内存错误: ```cpp void DoSomething( _In_reads_bytes_(numBytes) const void * p, _In_ size_t numBytes ); ``` 应用于指针参数的 **_In_reads_bytes_** 注解明确说明:该指针指向输入只读内存(_In_reads_),并且这个输入缓冲区的大小(以字节表示,_bytes_)由 `numBytes` 参数给出。 这样,我们依然保持了函数调用的清晰和简洁: ```cpp DoSomething(&data, sizeof(data)); ``` 同时添加了有助于代码分析器和其他工具发现内存错误的信息片段。 如果你想了解更多关于 SAL 注解的内容,可以从这篇 MSDN 文档入手: [使用 SAL 注解减少 C/C++ 代码缺陷](https://learn.microsoft.com/zh-cn/cpp/code-quality/using-sal-annotations-to-reduce-c-cpp-code-defects?view=msvc-170)

相似文章

关于C数组类型语义的讨论

Lobsters Hottest

本文解释了C数组类型的令人困惑的行为,包括它们退化为指针、sizeof和函数参数等例外情况,并将其与函数类型进行比较,提出了一种数组和指针严格分离的心理模型。

用C语言搞怪,第&((int*)-8)[3]部分

Lobsters Hottest

一篇幽默的教育性文章,涵盖C语言基础知识,如前向声明、运算符优先级、无条件跳转和基本算术运算,并附带有意搞怪的代码示例。

C语言中的一切皆为未定义行为

Hacker News Top

一位经验丰富的C++开发者认为,所有非平凡的C和C++代码都包含未定义行为,使得内存安全无法实现,并质疑这些语言在现代软件开发中的持续使用。