CppCon 2015 学习A Few Good Types

news2025/6/7 0:59:25

代码重构前后,用现代C++更安全、更简洁的方式来处理数组和长度问题,并且利用静态分析(SA,Static Analysis)工具来捕获潜在错误。

代码重构前(Before)

void f(_In_reads_(num) Thing* things, unsigned count) {
    unsigned totalSize = 0;
    for (unsigned i = 0; i <= count; ++i)  // 错误:i <= count,越界了!
        totalSize += things[i].GetSize();
    // 这里会越界访问 things[count]
    memcpy(dest, things, count);  // 错误:count 是元素个数,但 memcpy 需要字节数
    // 这些错误可以被静态分析工具检测出来
}
void caller() {
    Thing things[10]; // 未初始化数据
    f(things, 10);
}

问题点:

  • for循环条件写成了 i <= count,导致访问越界。
  • memcpy参数传入的 count 是元素数量,不是字节数,可能导致拷贝异常。
  • things数组未初始化,可能产生未定义行为。
  • 静态分析工具(SA)可以发现这些错误。

代码重构后(After)

void f(array_view<const Thing> things) {
    unsigned totalSize = 0;
    for (auto& thing : things)
        totalSize += thing.GetSize();
    std::copy(things.begin(), things.end(), dest);  // 用 std::copy 更安全且表达语义清晰
}
void caller() {
    Thing things[10]; // 未初始化数据
    f(things);          // 调用时传入整个数组
    f({things, 5});     // 明确只传入前5个元素
    f({things, 30});    // 错误,长度超过数组大小,静态分析会捕获
}

优势:

  • 使用 array_view(或者类似的 std::span)传入数据和长度,避免手动传递指针+长度的组合,减少越界风险。
  • 用范围for循环遍历,更简洁且不易错。
  • std::copy替代了 memcpy,类型安全,且语义更明确。
  • 调用时可以用花括号初始化,静态分析会检查长度是否合理,未初始化数据访问也会被警告。
  • 代码更现代、更安全,维护性更高。

总结

  • 避免手动管理指针和长度,使用范围封装(array_viewstd::span)可以减少越界和传参错误。
  • 用范围for和标准库算法替代传统的索引循环和C风格函数,更安全且更易读。
  • 静态分析工具结合现代C++特性能早期捕获潜在错误,提高代码质量。
  • 代码更符合现代C++最佳实践。
#include <span>
#include <iostream>
void f(std::span<const int> data) {
    for (auto v : data) std::cout << v << ' ';
    std::cout << '\n';
}
int main() {
    int arr[] = {1, 2, 3, 4, 5};
    f(arr);                // 自动转换成 std::span
    f(std::span(arr, 3));  // 传前3个元素的视图
}
cmake_minimum_required(VERSION 3.10)
project(HelloTestableWorld)
set(CMAKE_CXX_STANDARD 26)
# 添加你的源文件
set(SOURCES
    main.cpp
)
# 添加可执行文件(主程序)
add_executable(test ${SOURCES})

这是在介绍 Microsoft GSL (Guidelines Support Library) 的目标,核心思想是:

  • 从设计层面保障安全,避免危险的系统编程错误(例如内存错误)。
  • 阻止有缺陷的代码编译通过,提早在编译时发现问题。
  • 运行时检测缺陷,快速失败,避免隐藏bug导致更大问题。
  • 保持C++高效和对硬件的控制能力,即不牺牲性能。
  • 支持现代静态分析技术,方便开发时静态检查。
  • 明确类型的内存访问语义,帮助工具和程序员理解代码。
  • 深入洞察程序行为,尽早发现缺陷
  • 基于标准,跨平台且开源,方便社区使用和贡献。
  • 支持主流编译器和操作系统,涵盖 MSVC、Clang、GCC,Linux、Windows、macOS。

这段内容是在说 这些类型设计的具体目标,主要包括:

  • 内存安全
    这些类型确保 边界安全(bounds-safety),也就是说不会越界访问。同时还有机制避免悬空指针(dangling pointers)等问题。
    目的是取代非标准注解(如 Microsoft 的 SAL),用更现代和强类型的方式保证安全。
  • 类型安全
    防止不安全的类型转换,保证类型的一致性,避免因为类型错误引发的Bug。
  • 效率
    设计做到零开销(zero-overhead),也就是说它们的性能和写手写检查代码差不多,甚至更低开销。
    相比传统不安全代码,开销尽量小,做到安全和性能兼顾。
  • 抽象
    将关注点分离:数据访问(view)和数据存储(container)分开,这样更灵活、清晰,便于维护和扩展。
    总结来说,就是做一个既安全又高效的现代C++类型体系,帮助开发者写出健壮又性能优的代码

“注解”(Annotation)在编程中一般指的是给代码元素(比如函数、变量、类型)添加的额外信息或标记,用来辅助编译器、工具或开发者理解代码的用途、限制或行为。

具体来说:

  • 作用
    注解帮助工具进行静态分析、安全检查、代码生成或文档生成。
    比如告诉编译器某个指针参数不能为空,或者某个函数不会修改传入的参数。
  • 形式
    注解可以是特殊的注释、宏定义,或者语言本身支持的属性(attribute)。
    比如微软的SAL(Source Annotation Language)就是用一组宏注解函数参数,提示参数的行为。
  • 例子
    _In_ int* ptr   // 表示 ptr 是输入参数,不会被修改
    _Out_ int* ptr  // 表示 ptr 是输出参数,函数会写入数据
    _In_reads_(count) int* arr  // 表示 arr 是输入数组,有 count 个元素
    
    这些注解不是代码逻辑,只是给静态分析工具看的,帮助发现潜在错误。
  • 为什么要替换注解
    传统注解依赖宏定义和工具,语法不够现代和严谨,容易写错或忽略。
    新的类型安全设计用更强类型和接口来替代注解,直接从类型系统保证安全,效果更好,编译期错误更多。

总结的 array_view<ValueType, Extents...> 特性挺全面的,补充一点:

  • 核心思想array_view 不拥有数据,只是对已有连续内存区域的轻量“视图”。
  • 安全性:避免了裸指针直接操作多元素数组时容易出现的越界问题。
  • 高效:存储指针和长度(或编译时长度),复制开销很小,是值类型。
  • 灵活:支持动态长度和固定长度(编译时确定),固定长度版本在编译期可优化更多。
    使用场景举例:
void process(array_view<int> data) {
    for (int x : data) {
        // 处理每个元素
    }
}
// 调用时:
int arr[10];
process(arr);              // 传入动态长度视图
process(array_view<int, 10>{arr}); // 传入固定长度视图

这样既方便又安全,还能明确约束长度,减少出错可能。

关于 array_view 这一部分可以总结如下:

  • 存储开销极小
    • 对于动态长度的 array_view,它内部大致就是一个指针 T* 和一个大小 size_type,保存指向数据的指针和长度。
    • 对于固定长度(比如长度是模板参数),只需保存一个指针,长度在编译期已知,这样就没有额外开销(zero overhead)。
  • 不进行任何内存分配array_view 只是对现有内存区域的“观察视图”,不拥有内存。
  • 长度不可变:构造时确定后,长度不能变。
  • 访问总是带边界检查,一旦越界访问,程序立即失败(fail-fast),这有助于快速发现错误。
    结合提到的 CppCon 示例里的 read 函数代码,其实用裸指针写类似代码时极易出错,比如:
packet += sizeof(Foo);
Bar* bar = (Bar*)packet;
// 这里假设packet有效且足够大,但实际上没边界检查,可能越界
packet += sizeof(Bar);
FuzzBuzz* fuzzbuzz = (FuzzBuzz*)packet;
// 如果前面判断有误,fuzzbuzz指针可能越界或非法

如果用 array_view,可以替代裸指针和长度传递,更安全:

int read(array_view<char> packet) {
    size_t needed = sizeof(Foo) + sizeof(Bar) + sizeof(FuzzBuzz);
    if (packet.size() < needed)
        return -1;
    Foo* foo = reinterpret_cast<Foo*>(packet.data());
    // 安全使用 foo
    auto barView = packet.subspan(sizeof(Foo));
    Bar* bar = reinterpret_cast<Bar*>(barView.data());
    // 安全使用 bar
    auto fuzzbuzzView = packet.subspan(sizeof(Foo) + sizeof(Bar));
    FuzzBuzz* fuzzbuzz = reinterpret_cast<FuzzBuzz*>(fuzzbuzzView.data());
    // 安全使用 fuzzbuzz
}

这样不仅写得更清晰,边界也受到了严格保障。

#include <iostream>
#include <span>
#include <cstring>  // 用于模拟填充数据
struct Foo {
    int someEntry;
};
struct Bar {
    double someField;
};
struct FuzzBuzz {
    char anotherField[8];
};
int read(std::span<char> packet) {
    size_t needed = sizeof(Foo) + sizeof(Bar) + sizeof(FuzzBuzz);
    if (packet.size() < needed) return -1;
    Foo* foo = reinterpret_cast<Foo*>(packet.data());
    // 安全使用 foo
    std::cout << "Foo.someEntry = " << foo->someEntry << "\n";
    std::span<char> barSpan = packet.subspan(sizeof(Foo));
    Bar* bar = reinterpret_cast<Bar*>(barSpan.data());
    // 安全使用 bar
    std::cout << "Bar.someField = " << bar->someField << "\n";
    std::span<char> fuzzbuzzSpan = packet.subspan(sizeof(Foo) + sizeof(Bar));
    FuzzBuzz* fuzzbuzz = reinterpret_cast<FuzzBuzz*>(fuzzbuzzSpan.data());
    // 安全使用 fuzzbuzz
    std::cout << "FuzzBuzz.anotherField = ";
    for (char c : fuzzbuzz->anotherField) std::cout << c;
    std::cout << "\n";
    return 0;
}
int main() {
    // 模拟一块连续内存,大小刚好装得下三个结构体
    alignas(Foo) char buffer[sizeof(Foo) + sizeof(Bar) + sizeof(FuzzBuzz)] = {};
    // 在 buffer 中初始化三个结构体的内存
    Foo* foo = reinterpret_cast<Foo*>(buffer);
    foo->someEntry = 42;
    Bar* bar = reinterpret_cast<Bar*>(buffer + sizeof(Foo));
    bar->someField = 3.14159;
    FuzzBuzz* fuzzbuzz = reinterpret_cast<FuzzBuzz*>(buffer + sizeof(Foo) + sizeof(Bar));
    std::memcpy(fuzzbuzz->anotherField, "ABCDEFGH", 8);
    // 调用 read 函数
    int ret = read(std::span<char>(buffer, sizeof(buffer)));
    if (ret != 0) {
        std::cerr << "Packet too small!\n";
    }
    return 0;
}

Safety Features(安全特性)

  1. 安全类型转换:
    • array_view<int> 可以隐式转换成 array_view<const int>(允许增加 const 限定,安全)。
    • array_view<int> 不能转换成 array_view<short>(类型不兼容,编译失败)。
    • array_view<byte> 可以转换成 array_view<T>,只要 T 是简单布局类型(例如 POD 类型),这提供了一定的灵活性。
  2. 方便地从已有容器构造:
    • 可以从数组、std::arraystd::vector 等自动推断大小,简化代码。

固定大小的 array_view 提供更强的安全保障:

int arr[] = { 1, 2, 3, 4 };
array_view<int, 4> av4 = arr;    // 完全匹配,安全,视图长度固定4
array_view<int, 2> av2 = arr;    // 2 < 4,允许隐式转换(只看前两个元素)
av2 = av4;                      // ok,固定长度视图从更大的视图转到更小的视图允许
av4 = av2;                      // 错误,不能把小视图转换成大视图,编译失败
array_view<int> av_dyn = av2;    // 固定长度到动态长度允许
av4 = av_dyn;                   // 动态长度转固定长度,运行时会检测越界并快速失败

固定长度视图利用类型系统防止越界访问,编译期就能捕获错误。

用例对比:传统字符串处理和 cstring_view(字符串视图)

传统写 XML 代码:

void WriteXml(_In_reads_(cchText) PCSTR szText, size_t cchText)
{
    if ((size_t)-1 == cchText) // 特殊值表示未传长度
        cchText = strlen(szText);
    while (cchText)
    {
        if (*szText == '&')
            Write("&amp;", sizeof("&amp;"));
        else
            Write(szText, 1);
        cchText--;
        szText++;
    }
}

存在问题:

  • 需要特别处理未传长度情况,调用 strlen
  • 手动处理字符指针,容易出错。
  • 写法冗长。
    cstring_view 改写后:
void WriteXml(cstring_view text)
{
    for (auto c : text) // 自动边界检查,无法越界
    {
        if (c == '&')
            Write(ensure_z("&amp;"));
        else
            Write({&c, 1});
    }
}

优势:

  • 不需要 strlen 特殊处理。
  • 使用范围 for 循环,简洁且安全。
  • 访问时有边界检查,减少安全风险。
  • 代码更易读,意图明确。

总结

  • array_viewcstring_view 等视图类型,通过类型系统和运行时检查,实现更安全的内存访问和边界保护。
  • 这些类型可以有效防止越界访问、非法类型转换和未初始化内存使用。
  • 现代 C++ 代码借助这些类型,能写出更简洁、可维护且安全的代码。

string_view<CharType, Extent> 的核心特点

  • 视图(View)概念:它是对一段连续字符序列的“视图”,本身不拥有字符数据,只是“指针 + 长度”的组合。
  • 轻量级:复制和移动操作都很便宜,属于值类型(value type)。
  • 长度灵活:长度可以在编译时固定(固定长度)或运行时指定(动态长度)。
  • 无内存分配:不会分配新内存,只是现有字符数据的一个窗口。
  • 长度不可变:构造后长度不可更改,保证视图的稳定性。
  • 访问安全:所有访问都带有边界检查,防止越界。
  • 快速失败:一旦发生访问越界等错误,程序会快速失败(fail-fast),提高安全性。
    简单来说,string_view 是用来安全、高效地“观察”一段字符串内容,而不拷贝、不拥有数据,还能避免常见的越界错误。

string_view<CharType, Extent> 的本质

  • 本质上是 array_view 的别名
    具体定义如下:
    template <class CharT, size_t Extent = dynamic_range>
    using basic_string_view = array_view<array_view_options<CharT, unsigned short>, Extent>;
    
  • 封装了字符数组的安全视图,继承了 array_view 的所有特性,比如“指针+长度”、边界检查和无额外分配。
  • 提供了方便的别名,针对常用字符类型和常量类型
    • string_view —— 对 char 类型的字符串视图
    • cstring_view —— 对 const char 类型的字符串视图(只读)
    • wstring_view —— 对 wchar_t 类型的宽字符字符串视图
    • cwstring_view —— 对 const wchar_t 类型的宽字符字符串视图(只读)
      简而言之,string_view 其实是基于 array_view 实现的,更专注于字符串的安全、高效访问,且有多种别名方便不同字符类型使用。

string_view<CharType, Extent> 的特点与用法

  • 字符串操作成为自由函数
    诸如 findcomparetrim 等字符串相关的操作,不是成员函数,而是单独的函数,作用于 string_view,这样设计更灵活,减少类接口复杂度。
  • 当前的 GSL(Guideline Support Library)实现还需补充这些函数,比如添加字符串相关的自由函数,完善功能。
  • 对零终止(null-termination)不敏感
    string_view 并不要求字符串必须以 '\0' 结尾,这让它既可以表示传统 C 字符串,也可以表示任意的字符串切片。
  • 初始化时需要显式表示零终止字符串
    如果想从以 '\0' 结尾的 C 字符串初始化 string_view,需要用类似 ensure_z 这样的辅助函数,来告诉编译器这是一个零终止的字符串,从而正确初始化:
    void f(const char* s) {
        string_view sv = ensure_z(s); // 明确告诉是零终止字符串,初始化正确
        ...
    }
    
  • 优点
    这种方式增加了源码的信息表达,代码更明确,避免隐式假设,提升安全性和可维护性。
#include <string_view>
#include <cstring>  // 用于模拟填充数据
// 假设 string_view 是 gsl::basic_string_view<char>
std::string_view ensure_z(const char* s) {
    // 如果传入指针为空,返回空视图
    if (s == nullptr) {
        return std::string_view{};
    }
    // 计算字符串长度(不包含 '\0')
    size_t len = std::strlen(s);
    // 返回从指针和长度构造的 string_view
    return std::string_view{s, len};
}
void f(const char* s) {
    std::string_view sv = ensure_z(s);  // 明确告诉是零终止字符串,初始化正确
}
int main() {
    f("test");
    return 0;
}

array_view(或者类似的视图类型)常见的“提取子视图”功能,具体含义和用途如下:

提取子视图(sub-views)函数说明

  • 固定长度版(模板参数编译时指定长度)
    array_view<T, Count> first<Count>() const;   // 返回视图的前 Count 个元素
    array_view<T, Count> last<Count>() const;    // 返回视图的后 Count 个元素
    array_view<T, Count> sub<Offset, Count>() const;  // 返回从 Offset 开始长度为 Count 的子视图
    
  • 动态长度版(运行时指定长度)
    array_view<T> first(size_type count) const;  // 返回前 count 个元素的子视图
    array_view<T> last(size_type count) const;   // 返回后 count 个元素的子视图
    array_view<T> sub(size_type offset, size_type count) const;  // 从 offset 开始,取 count 个元素的子视图
    
  • 通用的截取函数
    array_view<T> section(size_type offset, size_type count) const;
    
    这个通常用于多维数组视图,也适合一维情况,截取从 offset 开始的 count 个元素的子视图。

作用和优点

  • 方便对数组或序列的一部分进行操作,无需拷贝数据。
  • 子视图仍然是轻量的,不涉及内存分配。
  • 编译时固定长度的版本能在类型层面确保安全性。
  • 运行时版本更灵活,适合长度不固定的情况。
  • 适合函数式编程风格,链式操作也很自然。

例如:

array_view<int> av = /* 假设长度是 10 */;
auto first_three = av.first(3);    // 取前3个元素
auto last_two = av.last(2);        // 取最后2个元素
auto middle = av.sub(3, 4);        // 从索引3开始取4个元素
#include <span>
#include <cstring>  // 用于模拟填充数据
int main() {
    int arr[] = {1, 2, 3, 3, 4, 5, 6, 7, 9, 10}; /* 假设长度是 10 */
    std::span<int> av(arr);
    auto first_three = av.first(3);  // 取前3个元素
    auto last_two = av.last(2);      // 取最后2个元素
    auto middle = av.subspan(3, 4);  // 从索引3开始取4个元素
    return 0;
}

这段话讲的是 array_view(或类似的视图类型)如何和旧代码(legacy code)配合使用的细节。

  • 构造函数支持 (T*, size_type)
    可以用一个裸指针和长度来构造视图对象。这种方式适合与老接口交互(比如C语言接口或旧的二进制接口 ABI),因为它们通常就是用裸指针和长度来传递数据。
  • 提供访问底层裸指针的接口 T* data()
    这样可以方便地把数据传给只接受裸指针的旧函数。
  • 使用这些裸指针操作是“信任我”的行为
    因为绕过了视图的边界检查,操作时不会自动安全检查。
    所以需要用 [[suppress(bounds)]] 之类的机制告诉静态分析器,这里是有意识地绕过了检查,程序员负责保证安全。
    总结:
    这种设计保证了现代安全视图类型能和旧代码兼容,但同时提醒开发者,使用裸指针要小心,可能会带来安全风险,需要手动确认安全性。

段话描述了 array_view(或类似的视图类型)相较于提案 N3851 的一些主要区别。

主要改动:

  1. 允许固定每个维度的大小
    • 这意味着可以在某些情况下指定特定维度的大小,并在编译时确定视图的大小,从而提高类型安全性和效率。
  2. 增加了从字节表示和到字节表示的转换
    • 新的操作如 as_bytes()as_array_view() 允许将视图转换为字节流表示或从字节流恢复视图。这对于需要按字节操作数据的场景非常有用。
  3. 增加了更多的构造函数,支持便捷的“插入式使用”
    • 提供了更多的构造函数选项,可以更方便地将不同类型的数据容器转换为 array_view 类型,使得 array_view 更易于与现有代码和容器集成。
  4. 在元素和字节之间描述长度
    • 新的设计允许通过 length()bytes() 函数分别返回元素数量和字节数。这对于不同的内存操作非常有用,特别是在处理原始内存块时,开发者需要能够明确知道数据的大小和长度。
  5. 允许指定用于测量/索引的大小类型
    • 可以为视图的索引操作指定一个特定的大小类型,从而在某些特定情况下,精确地控制如何表示和索引数据。
  6. 增加了更多的“切片和切分”操作
    • 新增的 first()last()sub() 等函数可以对视图进行分割,支持更灵活的数据操作和提取子视图的功能。

总结:

这些更改使得 array_view 类型更加通用、灵活和高效,可以更方便地与现有的代码进行集成,同时提供了更多的内存和数据操作功能,尤其是在处理多维数据或需要更细粒度控制的场景中。

描述了 string_viewLibrary Fundamentals TS 提案与其在 GSL(Guideline Support Library)中的实现之间的差异。以下是这些差异的详细解释:

主要差异:

  1. string_viewarray_view<CharType...> 的类型别名
    • string_view 被实现为 array_view 的一个特化版本。它是一个对字符数组的轻量级视图,提供了对字符数据的无缝访问。
  2. 增加了固定长度的能力
    • string_view 在某些情况下可以在编译时固定其长度。这是通过引入编译时已知的长度参数来实现的。这样可以提高类型安全性,并允许在编译时捕捉潜在的错误。
  3. 增加了字节表示的转换功能
    • string_view 提供了与字节表示的转换(例如:as_bytes())功能。允许将字符串视图转换为字节流,或者将字节流转换为字符串视图。这对于低级别的内存操作或需要按字节处理的场景非常有用。
  4. 同时描述元素和字节的长度
    • string_view 可以提供字符串的长度信息,不仅可以用元素的数量来表示长度,还可以用字节数来描述。这对于一些涉及原始内存或低级内存操作的操作非常方便。
  5. 允许为度量/索引指定大小类型
    • string_view 允许开发者为字符串的度量或索引操作指定一个特定的大小类型。这使得开发者可以在某些特定情境下更加灵活地控制索引操作的行为。
  6. 支持可变或不可变字符的字符串视图
    • string_view 可以用来处理指向可变字符数组或者不可变字符数组的视图。这使得它既可以用于处理不可修改的字符串(例如常量字符串),也可以用于处理需要修改的字符串数据。
  7. 要求从零结尾字符串显式构造
    • string_view 必须显式地从零结尾的字符串构造。这是因为零结尾的字符串是 C 风格字符串的常见表示方式,而 string_view 需要知道何时结束,因此不允许隐式的转换。
  8. 具有作为自由函数的特定于字符串的函数
    • string_view 提供了许多与字符串处理相关的自由函数(例如:查找、比较、裁剪等),使得 string_view 操作更为简洁和易用。

总结:

这些差异使得 string_viewLibrary Fundamentals TS 提案和 GSL 实现之间的灵活性和功能有所不同。GSL 版本提供了更多的控制选项(例如,可以支持固定长度、字节转换等),同时提高了与其他类型和底层数据操作的兼容性。

Early Lessons from Usage

1. Easy replacement at call sites – nearly always “just add braces”
  • 意思:将常规的指针和长度组合(如 foo(p, len))替换为 array_view 类型时,通常只需要在调用处添加大括号 {},即使没有做太大改动,也能顺利工作。
    • 举例
      foo(p, len);  // 传统做法,传递指针和长度
      foo({p, len});  // 使用 array_view 替代,自动推导类型并明确表达长度
      
    • 解释:通过添加大括号 {},就将一个指针和长度的组合变成了一个 array_view 类型的对象,编译器会根据上下文推导出类型。这种方式简化了代码修改。
2. Required little change inside callees besides length calculations
  • 意思:在函数内部,除了长度计算之外,几乎不需要对代码进行太多修改。
    • 举例
      • 传统做法:
        for (UINT i = 0; i < len; ++i) {
            // 处理数据
        }
        
      • 使用 array_view 后:
        for (UINT i = 0; i < p.length(); ++i) {
            // 处理数据
        }
        
    • 解释:当你将原来的指针和长度组合替换为 array_view 时,唯一需要修改的通常是 len 这个变量,因为你不再直接使用长度,而是调用 p.length() 获取视图的长度。
3. bytes/elements difference makes code clearer to read
  • 意思:在操作内存时,区分字节和元素的长度,使得代码更加清晰和易于理解。
    • 解释array_view 允许你明确地知道你正在处理的是字节数据还是按元素计算的数据。通过提供两种长度信息(字节数和元素数),代码更加明确和自文档化。例如,array_view 可以描述一个视图的元素数量或字节数量,避免了混淆。
4. Need to wrap standard and common libs to understand array_view<byte> (at the least)
  • 意思:要让标准库和常用库理解 array_view<byte>,可能需要进行适配或包装。
    • 解释:大多数现有的标准库函数(如 memcpy()memset()ZeroMemory()CopyMemory() 等)通常依赖于裸指针和长度参数。然而,array_view 提供了一个更加类型安全和自文档化的接口,但这些库函数并不直接支持 array_view 类型。因此,为了与这些标准库函数兼容,可能需要将 array_view<byte> 转换为裸指针或手动包装。

Performance: Key Insights

这段话描述了如何通过类型系统提供的保证和优化来提升性能,尤其是在使用像 array_view 这样的类型时,如何通过编译器优化来减少性能开销。

1. Optimization can leverage guarantees provided by type system
  • 意思:优化可以利用类型系统提供的保证。类型系统通过定义变量和类型的行为,使得编译器能做更多的优化。
    • 举例:通过使用 const 修饰符,我们告诉编译器某些变量不会改变。编译器可以利用这些信息进行更高效的优化,比如在编译时就知道某些变量值不会发生变化,避免重复计算。
2. Make it clear to the optimizer you are a simple, safe type
  • 意思:通过明确声明你的类型是简单且安全的,编译器可以做出更多优化。对于 array_view,它是一个简单的视图类型,表示一个指向元素的指针以及元素的长度。因为它不包含复杂的状态或动态分配,编译器可以更高效地进行优化。
3. Range-check optimizations are important and do-able
  • 意思:范围检查优化是可能实现的,并且对于性能非常重要。
    • 解释:传统的数组越界检查会带来性能开销,但对于 array_view 类型来说,编译器可以识别这些范围检查并进行优化。例如,可以通过提前消除冗余的范围检查来减少不必要的计算。
4. Hoisting and elimination optimizations
  • 意思:范围检查的优化包括“提升”(hoisting)和“消除”(elimination)。提升是将范围检查移到循环外面,从而避免在循环内部重复检查。消除是完全去掉不必要的范围检查(如果编译器可以证明它们是冗余的)。
5. MSVC already knows how to do efficient range-checking for .NET Native compilation
  • 意思:MSVC 已经知道如何为 .NET Native 编译进行高效的范围检查,并且可以使用 RNGCHK() 进行优化。并且,使用这种方法时,不需要像传统的垃圾回收、框架或运行时 DLL 那样的开销。

Example 1: Optimization with Simple C Code

typedef int my_array[9];
my_array glob;
void f(my_array a) {
    // a 是一个数组,编译器知道它的地址在这里不能改变
    for (int i = 0; i < len; ++i)
        a[i] = glob[i]; // 这行代码,编译器知道 glob 的地址不会改变
    // 循环是单调递增的
}
  • 解释:这里,aglob 都是固定数组类型。编译器知道 aglob 的指针不会改变,因此它可以将这些指针加载到寄存器中,并且循环的计算可以进行强度归约(即减少不必要的计算)。
  • 优化效果:这种简单的代码在编译时就能进行大量优化,最终会减少机器指令的数量,从而提高性能。

Example 2: Optimization with array_view

int arr[9] = { ... };
const array_view<int, 9> glob = arr; // const 表示 glob 的 int* 成员不会改变
void f(array_view<int, 9> p) {
    // p 包含一个单一的 int*,编译器知道从这里开始,int* 的值不会改变
    for (int i = 0; i < p.length(); ++i)
        a[i] = glob[i]; // 这行代码会进行范围检查
    // 循环依然具有和之前相同的属性
}
  • 解释:这里使用了 array_view<int, 9> 来替代传统的固定数组。array_view 在内部使用一个指针来引用数据,并且通过 length() 提供数组的长度。由于 array_viewconst 类型,编译器可以确保指针不会发生变化。
  • 优化
    • p 的指针成员可以通过寄存器传递,并且只加载一次。
    • 循环的强度归约可以继续进行,编译器可以优化循环。
    • 范围检查优化:即使代码中有范围检查,编译器可以通过证明该检查是冗余的来将其消除(消除掉无用的检查)。
    • 如果编译器无法消除检查,它可能会将范围检查提取到循环外部,从而避免在每次迭代时进行检查。

结论

通过这些优化,使用 array_view 和类似的简单类型可以显著提高性能。编译器利用类型系统的保证进行优化,使得代码更加高效。特别是在范围检查、强度归约和指针优化方面,array_view 的使用能够让编译器更容易进行这些优化,同时保持类型安全。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2400800.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

单元测试-断言常见注解

目录 1.断言 2.常见注解 3.依赖范围 1.断言 断言练习 package com.gdcp;import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test;//测试类 public class UserServiceTest {Testpublic void testGetGender(){UserService userService new UserService…

TDengine 在电力行业如何使用 AI ?

在你面前摆着一堆机器运行日志、传感器读数、电表数据&#xff0c;几十万、几百万条每秒增长的数据流&#xff0c;你会怎么处理&#xff1f;是加人、加脚本&#xff0c;还是干脆放弃实时分析&#xff1f; 过去&#xff0c;时序数据是工业的“副产品”&#xff1a;只是存着、查…

OpenCV C/C++ 视频播放器 (支持调速和进度控制)

OpenCV C/C 视频播放器 (支持调速和进度控制) 本文将引导你使用 C 和 OpenCV 库创建一个功能稍复杂的视频播放器。该播放器不仅能播放视频&#xff0c;还允许用户通过滑动条来调整播放速度&#xff08;加速/减速&#xff09;以及控制视频的播放进度。 使用opencv打开不会压缩画…

【Linux庖丁解牛】—自定义shell的编写!

1. 打印命令行提示符 在我们使用系统提供的shell时&#xff0c;每次都会打印出一行字符串&#xff0c;这其实就是命令行提示符&#xff0c;那我们自定义的shell当然也需要这一行字符串。 这一行字符串包含用户名&#xff0c;主机名&#xff0c;当前工作路径&#xff0c;所以&a…

Linux运维笔记:1010实验室电脑资源规范使用指南

文章目录 一. 检查资源使用情况&#xff0c;避免冲突1. 检查在线用户2. 检查 CPU 使用情况3. 检查 GPU 使用情况4. 协作建议 二. 备份重要文件和数据三. 定期清理硬盘空间四. 退出 ThinLinc 时注销&#xff0c;释放内存五. 校外使用时配置 VPN注意事项 总结 实验室的电脑配备了…

【Docker 从入门到实战全攻略(二):核心概念 + 命令详解 + 部署案例】

5. Docker Compose Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个 YAML 文件来配置应用服务&#xff0c;然后使用一个命令即可创建并启动所有服务。 基本命令 docker-compose up # 创建并启动所有服务 docker-compose down # 停止并移除容器、网络等…

【conda配置深度学习环境】

好的&#xff01;我们从头开始配置一个基于Conda的虚拟环境&#xff0c;覆盖深度学习&#xff08;如PyTorch&#xff09;和传统机器学习&#xff08;如XGBoost&#xff09;&#xff0c;并适配你的显卡&#xff08;假设为NVIDIA&#xff0c;若为AMD请告知&#xff09;。以下是完…

力扣4.寻找两个正序数组的中位数

文章目录 题目介绍题解 题目介绍 题解 题解链接&#xff1a;题解 核心思路&#xff1a;通过二分查找的确定分割点使左右两部分元素数量相等。 class Solution {public double findMedianSortedArrays(int[] nums1, int[] nums2) {int n1 nums1.length;int n2 nums2.length…

【相机基础知识与物体检测】更新中

参考&#xff1a; 黑马机器人 | 相机标定&物体检测https://robot.czxy.com/docs/camera/ 01-相机基础 相机基础概述 相机是机器视觉的基础&#xff0c;相机直接产生了相机数据。所有视觉算法都是作用在相机数据上的。相机数据的好坏&#xff0c;或者对相机数据的理解方式…

【前端】性能优化和分类

本页知识点参考&#xff1a;https://zhuanlan.zhihu.com/p/514222781 1. 加载性能优化 1.1 网站性能优化 content方法&#xff1a; 1&#xff09;减少HTTP请求&#xff1a;合并文件&#xff0c;CSS精灵&#xff0c;inline Image 2&#xff09;减少DNS查询&#xff1a;DNS缓存&…

PPO和GRPO算法

verl 是现在非常火的 rl 框架&#xff0c;而且已经支持了多个 rl 算法&#xff08;ppo、grpo 等等&#xff09;。 过去对 rl 的理解很粗浅&#xff08;只知道有好多个角色&#xff0c;有的更新权重&#xff0c;有的不更新&#xff09;&#xff0c;也曾硬着头皮看了一些论文和知…

rk3588 上运行smolvlm-realtime-webcam,将视频转为文字描述

smolvlm-realtime-webcam 是一个开源项目&#xff0c;结合了轻量级多模态模型 SmolVLM 和本地推理引擎 llama.cpp&#xff0c;能够在本地实时处理摄像头视频流&#xff0c;生成自然语言描述&#xff0c; 开源项目地址 https://github.com/ngxson/smolvlm-realtime-webcamhttps…

Rust 学习笔记:Box<T>

Rust 学习笔记&#xff1a;Box Rust 学习笔记&#xff1a;Box<T\>Box\<T> 简介使用 Box\<T\> 在堆上存储数据启用带有 box 的递归类型关于 cons 列表的介绍计算非递归类型的大小使用 Box\<T\> 获取大小已知的递归类型 Rust 学习笔记&#xff1a;Box<…

操作系统学习(十三)——Linux

一、Linux Linux 是一种类 Unix 的自由开源操作系统内核&#xff0c;由芬兰人 Linus Torvalds 于 1991 年首次发布。如今它广泛应用于服务器、桌面、嵌入式设备、移动设备&#xff08;如 Android&#xff09;等领域。 设计思想&#xff1a; 原则描述模块化与可移植性Linux 内…

NLP学习路线图(二十二): 循环神经网络(RNN)

在自然语言处理&#xff08;NLP&#xff09;的广阔天地中&#xff0c;序列数据是绝对的核心——无论是流淌的文本、连续的语音还是跳跃的时间序列&#xff0c;都蕴含着前后紧密关联的信息。传统神经网络如同面对一幅打散的拼图&#xff0c;无法理解词语间的顺序关系&#xff0c…

每日一C(1)C语言的内存分布

目录 代码区 常量区 全局/静态区 初始化数据段&#xff08;.data&#xff09; 未初始化数据段&#xff08;.bss&#xff09; 堆区 栈区 总结 今天我们学习的是C语言的内存分布&#xff0c;以及这些分区所存储的内容和其特点。今天的思维导图如下。 C语言作为一款直接处…

Photoshop使用钢笔绘制图形

1、绘制脸部路径 选择钢笔工具&#xff0c;再选择“路径”。 基于两个点绘制一个弯曲的曲线 使用Alt键移动单个点&#xff0c;该点决定了后续的曲线方向 继续绘制第3个点 最后一个点首尾是同一个点&#xff0c;使用钢笔保证是闭合回路。 以同样的方式绘制2个眼睛外框。 使用椭…

应用层协议:HTTP

目录 HTTP&#xff1a;超文本传输协议 1.1 HTTP报文 1.1.1 请求报文 1.1.2 响应报文 1.2 HTTP请求过程和原理 1.2.1 请求过程 1、域名&#xff08;DNS&#xff09;解析 2、建立TCP连接&#xff08;三次握手&#xff09; 3、发送HTTP请求 4、服务器处理请求 5、返回H…

复习——C++

1、scanf和scanf_s区别 2、取地址&#xff0c;输出 char ba; char* p&b; cout<<*p; cout<<p; p(char*)"abc"; cout<<*p; cout<<p; cout<<(void*)p; 取地址&#xff0c;把b的地址给p 输出*p&#xff0c;是输出p的空间内的值…