Sp.h 是 C 语言应得的标准库

Hacker News Top 工具

摘要

sp.h 是一个 15000 行的单头文件 C99 标准库,它绕过 libc 以提供可移植、显式且无堆的原始接口。其旨在用现代的系统调用级抽象取代传统的 libc。

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

缓存时间: 2026/05/23 06:29

# sp.h 是 C 语言应得的标准库 来源:https://spader.zone/sp/ 过去一年里,我一直在通过为 C 语言提供高质量、超便携的标准库来修正它。它**不是**在 libc 之上的简单封装;除非平台要求,否则不依赖 libc。据我所知,没有类似的东西存在。 这个库叫做 `sp.h` 1 (https://spader.zone/sp/#fn:1)。它是一个用纯 C99 编写的 15,000 行单头文件库。你可以在 GitHub (https://github.com/tspader/sp) 上找到源代码,其中包括库本身、大量示例程序以及六个扩展核心的棒球库 2 (https://spader.zone/sp/#fn:2)。如果你更倾向于阅读一些示例并浏览源码,可以先前往 GitHub。否则,让我们开始介绍吧! ## 目录 - 目录 (https://spader.zone/sp/#table-of-contents) - 原则 (https://spader.zone/sp/#principles) - 直接针对系统调用编程 (https://spader.zone/sp/#program-directly-against-syscalls) - Libc 具有主动危害性 (https://spader.zone/sp/#libc-is-actively-harmful) - 没有堆 (https://spader.zone/sp/#there-is-no-heap) - 以空字符结尾的字符串是魔鬼的杰作 (https://spader.zone/sp/#null-terminated-strings-are-the-devils-work) - 成为软件的一部分,而不是附属品 (https://spader.zone/sp/#be-a-part-of-your-software-not-aside-from-it) - 极致可移植 (https://spader.zone/sp/#be-extremely-portable) - 明确清晰 (https://spader.zone/sp/#be-explicit) - 非目标 (https://spader.zone/sp/#non-goals) - 与现有接口保持一致 (https://spader.zone/sp/#conformance-to-existing-interfaces) - 支持晦涩的架构和操作系统 (https://spader.zone/sp/#obscure-architectures-and-oses) - 性能 (https://spader.zone/sp/#performance) - 最后的思考 (https://spader.zone/sp/#a-parting-thought) - C 的价值在于其简洁 (https://spader.zone/sp/#c-is-valuable-because-its-simple) - 我想与你合作 (https://spader.zone/sp/#i-want-to-work-with-you) ## 原则 ### 直接针对系统调用编程 基本思想是:任何 C 标准库**必须**直接针对可用的最低层原语编写 3 (https://spader.zone/sp/#fn:3)。试图模拟、生成或对接操作系统与你自己的代码之间积累的几十年技术债务,既无用处也无成效。 ### Libc 具有主动危害性 遵循 libc 颇具诱惑力,因为大量代码承诺:只要你提供了一个 libc 实现,它们就能编译和运行。但如今,这越来越不成立了。 Libc 不为任何程序提供有用的接口。简单程序宁愿使用高级语言。复杂程序无法用 libc 提供的原语编写。过去十年间,异步编程愈发重要,这一问题更加严重。“快速”程序越来越多地取决于使用合适的内核原语进行 IO,而非比编译器更好地解决寄存器分配等问题。 任何以 `FILE*` 为基本 IO 单位,或认为子串是错误概念的接口,不仅仅令人烦恼——它是有害的。`sp.h` 将其抛在一边 4 (https://spader.zone/sp/#fn:4)。 ### 没有堆 以下类型支撑着整个库: ```c typedef enum { SP_ALLOCATOR_MODE_ALLOC, SP_ALLOCATOR_MODE_FREE, SP_ALLOCATOR_MODE_RESIZE, } sp_mem_alloc_mode_t; SP_TYPEDEF_FN( void*, sp_allocator_fn_t, void* user_data, sp_mem_alloc_mode_t mode, u64 size, void* ptr ); typedef struct sp_allocator_t { sp_allocator_fn_t on_alloc; void* user_data; } sp_mem_t; ``` 它们通过强制程序接受“凭空分配任意大小内存的能力”并非原语,而是一种虚构来实现这一点。内存并非属于“运行时”——内存属于你的程序。 ### 以空字符结尾的字符串是魔鬼的杰作 我过去曾写过相关文章 (https://spader.zone/sp-001#stringh-is-a-special-kind-of-hell): > 以空字符结尾的字符串意味着你无法: > - 返回一个非拥有关系的子串 > - 在 O(1) 时间内获知字符串长度 > - 编写能返回符合人体工程学的源码视图的词法分析器和解析器 > - 在不产生无效中间值的情况下构建字符串 > 此外,当然还有因缺失空终止符而导致的数量庞大的 bug 和安全问题。C 语言现代化的第一步就是完全摒弃以空字符结尾的字符串,转而使用简洁的 `sp_str_t`。 我曾以为唯一的缺点是:为了与任何其他 C API 对接,你不得不额外复制一份数据。但我逐渐发现这**完全无关紧要**。 一个原生围绕“指针+长度”字符串构建的 C 标准库**惊人地**符合人体工程学。例如,一个 `wc` 克隆的片段: ```c sp_str_t content = sp_zero; sp_io_read_file(mem, path, &content); sp_ht(sp_str_t, u32) counts = sp_zero; sp_str_ht_init(mem, counts); sp_da(sp_str_t) lines = sp_str_split_c8(mem, content, '\n'); sp_da_for(lines, i) { sp_da(sp_str_t) words = sp_str_split_c8(mem, lines[i], ' '); sp_da_for(words, j) { u32* count = sp_str_ht_get(counts, words[j]); if (count) { *count = *count + 1; } else { sp_str_ht_insert(counts, words[j], 1); } } } ``` 如果你的第一反应是“那又怎样?”,那么,没错,这就是重点。这段 C 代码读起来几乎和任何高级语言一样,**但同时**在解析时从不复制源缓冲区中的数据。换言之,它既是最符合人体工程学的版本,**也是最**高性能的版本。 ### 成为软件的一部分,而不是附属品 这个库可以被阅读、修改、调整、重写——或者任何你为了让它服务于你的目的而需要的操作。我为此付出了很多努力: - 库的核心约 40 个系统调用,它们是唯一的平台特定代码 5 (https://spader.zone/sp/#fn:5) - 库以单个文件形式发布,无需配置 - 文件组织极其清晰,并用 `@tag` 标记以便人类或 LLM 搜索 - 每个函数都属于一个命名空间 在 C 语言令人沮丧的部分试图通过精心设计的虚构来隐藏你的程序所运行的操作系统时,`sp.h` 则力求只统一那些真实存在的东西,尽可能薄地保持有用性,然后在你获得的完全相同原语之上构建功能。 ### 极致可移植 `sp.h` 用 C99 编写,可以针对任何可想象的编译器和 libc 编译。它在 Linux、Windows、macOS 上运行。它在 WASM 宿主下运行。它在浏览器 (https://spader.zone/prompt) 中运行。它与 MSVC 和 MinGW 一起工作;它可以在有或无 libc 的情况下工作,也可以与像 Cosmopolitan 这样的怪异 libc 一起工作。它与大编译器一起工作,也与 TCC 一起工作。 最棒的是,它能够做到所有这些,是**因为**它很小,而不是因为它很大。 ### 明确清晰 每次我选择隐式而非显式时,事后都感到后悔,并付出了修正的代价: - 错误总是返回并由调用者处理 - 程序没有可变全局状态 - 需要分配的函数接受一个分配器 - 内存初始化为零 ## 非目标 ### 与现有接口保持一致 这不是 libc。在必要时,`sp.h` 会**尊重** libc,并且在嵌入到使用 libc 的程序中时,它总能毫不突兀地完整工作。但它**不是** libc,你不应期望它表现得像 libc。 ### 支持晦涩的架构和操作系统 我为 x86_64 和 aarch64 编写代码。WASM 变得越来越重要,但仍次于原生目标。我不打算为了支持极小部分用例而使库膨胀。 话虽如此,如果你有兴趣在不支持的平台上使用该库,我非常乐意提供帮助,并且如果补丁合理,也乐意将其合并。 ### 性能 简而言之,库的立场是:在低层、计算密集型性能方面,**得不偿失**。 针对未知用例和未知硬件设计高性能软件和数据结构**极其**困难,结果代码也复杂得多。即便如此,当性能至关重要时,通常最好使用针对你实际用例和硬件编写的代码。 可能不考虑的事项包括: - SIMD - 高度优化的哈希表重写 - 找出内联或 `LIKELY` 如何让编译器生成更好的代码 可能考虑的事项包括: - 提供正确抽象以进行优化和/或零拷贝 IO - 编写不要求复制数据的 API 当然,在损害用户利益的情况下进行细粒度优化总是在考虑之列。修复 bug 总是在考虑之列。我并非反对优化;只是很忙。 ## 最后的思考 人们自然可能会问:你为什么要这么做?现在从未有过更多或更好的系统编程语言。为什么不直接用其中之一? 答案是:C 占据了一个真实的利基市场,而且并非完全建立在遗留代码之上。据我所知,它是唯一一种语言: - 可以直接编译成任何可以想象的机器代码 - 拥有最先进的优化编译器生态系统 - 用与操作系统和大多数库相同的语言编写 - **你作为个人项目**可以编写一个合理的编译器 换言之…… ### C 的价值在于其简洁 当然,这些说法在不同程度上都不公平。LLVM 存在,所以严格来说每个人都有一个 SOTA 编译器。大多数语言都有 FFI 和工具。最好的系统语言在 C 所擅长的事情上比 C 做得更好。 然而,拥有一种如此支持良好、如此优化、如此与编写原生代码的平台紧密相连、并且**如此平易近人**的语言,是神奇的。 ## 我想与你合作 陌生朋友,我最渴望的就是结识你并/或帮助你使用这个库。我会帮助你把它移植到你奇怪的环境中。我会向你解释它的任何部分。当你告诉我不擅长编程时,我会礼貌地倾听。我**当然**不是系统编程的天才;我所拥有的一切,都是源于对软件和计算机工作方式的严重误解,然后付出了大量辛勤工作、乐趣和更多的软件。 我在 Discord 服务器 (https://discord.gg/K3XUzDSnNP) 上,或者你可以在 IRC (https://web.libera.chat/?channel=#sp.h) 上的 #sp 找到我。你也可以给我发电子邮件。域名与这个网站相同,句柄是我的姓氏 6 (https://spader.zone/sp/#fn:6)。

相似文章

C++ 标准库在过去十五年间一直在自我撤步,证据公开

Lobsters Hottest

一份详细的目录,列出了从 C++11 到 C++26 期间被正式弃用、非正式不推荐或由于 ABI 约束实际上已损坏但无法修复的 C++ 标准库特性。文章指出,C++ 委员会推出一系列替代品来替换其自身特性的模式始终如一,其中包含一个基准测试,显示 Rust 和 C++ 标准库容器之间的 P99 延迟差异高达 58 倍。

C++26:标准库强化

Lobsters Hottest

C++26 引入了标准化的库强化机制,用于在运行时捕获常见的未定义行为(如越界访问)。基于 Google 的生产经验,此举仅带来 0.30% 的性能开销,同时将段错误减少了 30%。

关于C扩展、可移植性和替代编译器

Lobsters Hottest

本文讨论了编写可移植C代码的实际挑战,这些挑战源于对非标准编译器扩展和glibc条件头文件的依赖,并通过构建C编译器的示例进行说明。

XS: 一种编程语言。随时随地,人人可用。

Hacker News Top

XS是一种新的编程语言,以单个静态链接的二进制文件形式分发,其中包含编译器、语言服务器、调试器、格式化器、代码检查器、测试运行器、性能分析器和包管理器。它可以在多个平台上无需修改地运行,包括Linux、macOS、Windows、WASI、iOS、Android、ESP32和Raspberry Pi,并提供多种后端,包括JIT、虚拟机以及到C和JavaScript的转译器。