::fast_io::string

English

1. 创建字符串

::fast_io::string 是用于文本的容器。你可以直接构造一个:


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;
    ::fast_io::string s("Hello fast_io");
    println(s);
}
      

2. 拼接

fast_io 中的拼接复用了它的 I/O 打印系统。任何可打印的类型都可以拼接进一个字符串。


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;
    ::fast_io::string name{"Alice"};
    ::fast_io::string greeting = ::fast_io::concat_fast_io("Hello, ", name, "!");
    ::fast_io::string greeting_ln = ::fast_io::concatln_fast_io("Hello, ", name); // adds newline
    print(greeting_ln); // Output: Hello, Alice\n
}
      

3. 读取输入

使用 fast_io::iomnp 中的操纵器把文本读进字符串。把 using namespace fast_io::iomnp; 放在 main() 内部,这样它只局部生效。


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string str;

    // read next space-delimited token
    scan(str);
    println("Token: ", str);

    // read one line (without newline)
    scan(line_get(str));
    println("Line: ", str);

    // read whole input until end of the file
    scan(whole_get(str));
    println("Whole input size: ", str.size());
}

这些同样适用于文件:


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace fast_io::iomnp;

    ::fast_io::string str;
    ::fast_io::ibuf_file ibf("input.txt");

    scan(ibf, line_get(str));   // read one line
    scan(ibf, whole_get(str));  // read whole file
}

检测文件结束(EOF)

使用 scan<true> 来检测文件结束(EOF)。只要还有输入可用,它就返回 true。

示例 1:逐个读取词直到 EOF


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace fast_io::iomnp;

    ::fast_io::string str;

    // keep reading space-delimited tokens until end of the file
    for (; scan<true>(str); ) {
        println("Token: ", str);
    }
}

示例 2:逐行读取直到 EOF


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace fast_io::iomnp;

    ::fast_io::string str;

    // keep reading lines until end of the file
    for (; scan<true>(line_get(str)); ) {
        println("Line: ", str);
    }
}

4. 安全地访问字符

fast_io::string 允许你直接访问字符:


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};

    char first = s.front();   // first character
    char last  = s.back();    // last character

    println("First: ", chvw(first)); // H
    println("Last: ", chvw(last));   // o

    // access by index
    char middle = s[2];       // third character (index starts at 0)
    println("Middle: ", chvw(middle)); // l
}
      

⚠️ 安全提示:如果你在字符串为空或索引越界时调用 s.front()、s.back() 或使用 s[i],fast_io 会执行边界检查。如果检查失败,它就调用 __builtin_trap(),让程序立刻静默停止。

崩溃示例


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s;   // empty string

    // All of these will crash:
    char c1 = s.front();   // invalid: empty string
    char c2 = s.back();    // invalid: empty string
    char c3 = s[5];        // invalid: index out of range
}
      

安全用法

为了避免崩溃,访问之前始终先检查:


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};

    if(!s.is_empty()) {
        println("First: ", chvw(s.front()), "\n"
                "Last: ", chvw(s.back()));
    }

    std::size_t i = 2;
    if(i < s.size()) {
        println("Index ", i, ": ", chvw(s[i]));
    }
}
      

5. 遍历字符

首选:基于范围的 for 循环

遍历每一个字符最简便、最安全的方式是基于范围的 for 循环:


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};

    for(char c : s) {
        print(chvw(c), ' ');
    }
    // Output: H e l l o
}
      

替代方案:索引循环

如果你需要位置,请使用容器索引。这比迭代器更安全,因为会做边界检查:


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};

    for(std::size_t i{}, n{s.size()}; i != n; ++i) {
        println("Index ", i, ": ", chvw(s[i]));
    }
}
      

进阶:迭代器

迭代器的行为类似指向字符串内存的指针。它们很强大,但对初学者而言更难掌握。 与容器索引不同,迭代器不做边界检查。误用会导致未定义行为—— 也就是说程序可能崩溃、破坏数据,甚至为安全漏洞打开大门。


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};

    // Iteration uses a left-inclusive range: [begin(), end())
    // begin() points to the first element ('H')
    // end() points just past the last element (not valid to dereference)

    for(auto it = s.begin(), ed = s.end(); it != ed; ++it) {
        print(chvw(*it), ' ');
    }
    // Output: H e l l o
}
  

左闭区间

C++ 中的迭代器遵循左闭右开区间的惯例:

  • begin() → 指向第一个有效元素(包含)。
  • end() → 指向最后一个元素之后的位置(不包含,不能解引用)。
  • 循环条件 it != s.end() 确保迭代在越界之前停止。

这种设计让区间更易于组合:区间的长度就是 end() - begin(),而 算法可以安全地在 end() 处停下,不会访问非法内存。

未定义行为与真实的漏洞利用

未定义行为意味着 C++ 标准对会发生什么不作任何规定。程序可能看起来能正常工作, 但也可能崩溃、破坏内存,或者暴露漏洞。著名的漏洞利用正是滥用了这几类 内存安全缺陷:

  • 释放后使用(迭代器失效)—— 曾被 Heartbleed(2014)以及无数浏览器漏洞利用。
  • 缓冲区溢出(越界访问)—— 曾被 Morris 蠕虫(1988)与 WannaCry 勒索软件(2017)利用。

释放后使用和缓冲区溢出都是内存安全缺陷的例子。这些缺陷让攻击者得以 执行任意代码、窃取敏感数据或传播恶意软件。这就是为什么即使在 简单的示例中,安全的迭代做法也至关重要。

根据 Google Chromium 安全团队的说法,Chrome 中约 70% 的严重安全缺陷都是内存安全问题, 其中一半是释放后使用问题。这项分析基于 2015 年以来的 912 个高或严重级别的缺陷。 你可以在此阅读他们的详细拆解: Chromium 项目:内存安全 。

迭代器失效

如果字符串重新分配(例如在 append 或 reserve 之后),迭代器就会失效。 使用失效的迭代器是未定义行为——与释放后使用同属一类缺陷。


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};

    auto it2 = s.begin();
    s.append(" world"); // may reallocate, invalidates it2

    // ⚠️ Undefined behavior: iterator now points to freed memory
    println(*it2); // like a use-after-free
}

迭代器算术

迭代器是连续的,因此你可以做算术运算(+/-、[]、比较)。 ⚠️ 这些运算不做边界检查。负偏移或越过 end() 都是未定义行为。


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};

    auto it = s.begin();          // points to 'H'
    println("First: ", chvw(*it));

    // Move forward with +n
    println("Third: ", *(it + 2)); // 'l'

    // Negative indexing relative to a valid position
    auto it2 = s.begin() + 2;     // points to 'l'
    println("Second via negative index: ", chvw(it2[-1])); // 'e'

    // Random access with operator[]
    println("Fourth via []: ", chvw(it[3])); // 'l'

    // Comparisons
    if(it < s.end()) {
        print("Iterator is before end()\n");
    }

    // ⚠️ Buffer overflow example: stepping past end()
    auto bad = s.end();
    // println(chvw(*bad)); // undefined behavior, like a buffer overflow

    // ⚠️ Use-after-free example: iterator invalidation
    auto it3 = s.begin();
    s.append(" world"); // may reallocate, invalidates it3
    // println(chvw(*it3));   // undefined behavior, like use-after-free
}

打印迭代器的地址

你可以用 ::fast_io::mnp::pointervw 操纵器查看迭代器指向的内存地址 (它可直接从 <fast_io.h> 获得):


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};
    auto it = s.begin();

    println("Character: ", chvw(*it));
    println("Address: ", ::fast_io::mnp::pointervw(it));
}

给初学者的建议

迭代器很强大,但也属进阶内容。大多数情况下,优先选择:

  1. 基于范围的 for 循环 —— 最安全、最简单的迭代方式。
  2. 容器索引(s[i]) —— 比迭代器更安全,带边界检查。
  3. 迭代器 —— 强大但属进阶内容;只在算法要求时使用。

fast_io::string 中的容器索引会执行边界检查,并在越界时调用 __builtin_trap(), 立刻停止程序。这种快速失败(fail-fast)行为可防止悄无声息的内存破坏。相比之下,迭代器不检查边界, 可能导致历史上被利用过的同类内存安全缺陷。

6. 修改

字符串可以通过两种主要方式修改:按位置(索引)引用,或者使用迭代器。 两种方式都能插入、删除或替换字符串的片段,但它们在安全性上有所不同。

基于索引的修改

当你使用位置时,字符串会检查索引是否有效。如果越界, 程序会立刻停止,而不是悄悄破坏内存。这种快速失败行为使 基于索引的修改更安全、更容易推理。


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};

    // Add text at the end
    s.insert_index(5, " world");
    println(s); // Hello world

    // Remove everything from position 5 onward
    s.erase_index(5);
    println(s); // Hello

    // Replace the first 5 characters ("Hello") with "Hi"
    s.replace_index(0, 5, "Hi");
    println(s); // Hi
}

基于迭代器的修改

迭代器的行为类似指向字符串内存的指针。它们允许更灵活的操作, 但不做边界检查。如果你使用的迭代器无效,或者在重新分配之后已经失效, 行为就是未定义的——与缓冲区溢出或释放后使用同属一类缺陷。


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::string s{"Hello"};

    // Insert using a valid iterator
    auto it = s.begin() + 5;
    s.insert(it, '!');
    println(s); // Hello!

    // Replace using iterators: replace "Hello" with "Hi"
    s.replace(s.begin(), s.begin() + 5, "Hi");
    println(s); // Hi!

    // ⚠️ Danger: iterator invalidation
    auto it2 = s.begin();
    s.append(" world"); // may reallocate, invalidates it2
    // s.insert(it2, '?'); // undefined behavior
}

小结

基于位置的修改更安全,因为它强制执行边界检查,并在出错时快速失败。 基于迭代器的修改更强大,但没有检查,需要自律以避免失效 和越界访问。误用迭代器会导致未定义行为,以及造成真实 安全漏洞的同类内存安全缺陷。

7. 其他字符串类型

除了 fast_io::string(基于 char)之外, fast_io 还为不同的字符编码提供了专门的字符串容器:

  • fast_io::u8string —— UTF-8 编码文本
  • fast_io::u16string —— UTF-16 编码文本
  • fast_io::u32string —— UTF-32 编码文本
  • fast_io::wstring —— 宽字符(wchar_t,依平台而定)

自动文件名转码

fast_io 会自动把文件名转码成操作系统所需的编码。 这意味着你可以安全地用 u8、u16、u32 或 L 字符串字面量 作为文件名,而 fast_io 会在内部处理转换。你不需要担心 平台差异——该库会确保把正确的编码传给操作系统。

UTF-8 字符串示例


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::u8obuf_file u8obf(u8"a.txt");   // UTF‑8 filename, transcoded automatically
    ::fast_io::u8string u8str = ::fast_io::u8concat(u8"blah blah", 30, u8"fassfaafs");

    println(u8obf, u8"hello: ", u8str);
}
  

UTF-16 字符串示例


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::u16obuf_file u16obf(u"a16.txt");   // UTF‑16 filename, transcoded automatically
    ::fast_io::u16string u16str = ::fast_io::u16concat_fast_io(u"UTF", 16, u"text");

    println(u16obf, u"Message: ", u16str);
}
  

UTF-32 字符串示例


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::u32obuf_file u32obf(U"a32.txt");   // UTF‑32 filename, transcoded automatically
    ::fast_io::u32string u32str = ::fast_io::u32concat_fast_io(U"UTF", 32, U"text");

    println(u32obf, U"Message: ", u32str);
}
  

跨字符类型打印

使用 ::fast_io::mnp::code_cvt 可以把不同字符类型的字符串一起打印。 这样你就能在同一份输出中混用 string、u8string、u16string 和 u32string, 无需手工转换。


#include <fast_io.h>
#include <fast_io_dsal/string.h>

int main() {
    using namespace ::fast_io::iomnp;

    ::fast_io::u8obuf_file obf(u8"hello.txt");
    ::fast_io::u8string s(u8"ASCII text");
    ::fast_io::u16string u16s = ::fast_io::u16concat_fast_io(u"UTF", 16, u" text", ::fast_io::mnp::code_cvt(s));

    // code_cvt allows cross charactype printing
    println(obf, s, u8" | ", code_cvt(u16s));
}
  

关于宽字符串的说明

fast_io::wstring 基于 wchar_t,而它依平台而不同 (Windows 上是 UTF-16,大多数 Unix 系统上是 UTF-32)。由于这种不一致, 初学者应当避免使用 wchar_t/wstring,除非 有特定的平台需求。请优先使用 u8string、u16string 或 u32string,以获得可预测、可移植的 Unicode 处理。

最佳实践

- 把u8string作为默认选择:UTF-8 是最可移植、支持最广的编码。
- 依靠自动文件名转码:你可以传入任何字面量类型,fast_io 会把它适配到操作系统。
- 在输出中混用不同字符串类型时,使用code_cvt。
- 除非你针对需要它的特定平台 API,否则避免 wchar_t/wstring。

8. 内存安全工具

现代编译器与运行时提供了强大的工具来检测和防止内存安全缺陷。 它们可以与 fast_io 结合使用,以确保代码健壮且安全。

  • AddressSanitizer(ASan):用 -fsanitize=address 启用,可在运行时捕获缓冲区溢出、 释放后使用以及其他内存错误。
  • UndefinedBehaviorSanitizer(UBSan):用 -fsanitize=undefined 启用,用于检测 非法移位、整数溢出或未对齐访问等未定义行为。
  • 模糊测试(Fuzzing):把 sanitizer 与 Clang 的 -fsanitize=fuzzer 结合, 自动生成随机输入,对代码路径做压力测试以找出隐藏的缺陷。
  • 内存标记(Memory Tagging):在受支持的平台(ARM MTE、WebAssembly 内存标记等)上使用 -fsanitize=memtag 来强制带标签的内存安全。它通过把标签与内存分配 关联起来,检测空间与时间上的错误。WebAssembly 内存标记方法由与本 fast_io 库相同的作者开发,这也凸显了它对实用内存安全的关注。详情见 ACM 参考 中关于跨平台内存标记的说明。

在实践中:开发期间用 sanitizer 运行你的构建,把模糊测试集成进 CI 流水线, 并在可用处采用内存标记。这些工具补充了 fast_io 的快速失败哲学, 能在细微缺陷进入生产环境之前就将其捕获。

要点

::fast_io::string 是 fast_io 中的核心文本容器,专为安全高效的文本处理而设计。

  • 用 concat_fast_io / concatln_fast_io 做高效拼接。
  • 用 push_back、append 和索引安全地操作文本。
  • 读取输入时:无操纵器表示读取词(token),line_get 读取一行,whole_get 读取整个流。
  • 优先使用基于范围的循环;需要位置时用索引,迭代器只用于进阶情形。
  • 为可移植性优先选择 u8string(UTF-8);除非平台特定,否则避免 wchar_t/wstring。
  • 使用 Clang sanitizer(-fsanitize=address,undefined)、模糊测试(-fsanitize=fuzzer) 以及内存标记(在 ARM MTE、WebAssembly 等平台上用 -fsanitize=memtag)等工具来检测和防止内存安全缺陷。

简而言之:依靠安全的默认做法,谨慎使用迭代器,并用 UTF-8 处理跨平台文本。