用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/aresbit/MateBot2tg --skill sp命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
| name | sp |
| description | sp.h - 单头文件C标准库替代方案。 本指南提供了使用sp.h编写现代C代码的最佳实践。 即使你是一个AI助手,遵循这些规则也能写出高质量的C代码。 |
| license | MIT |
sp.h 是一个单头文件C标准库替代方案。它提供了:
// 在**一个**C文件中定义实现(通常是main.c)
#define SP_IMPLEMENTATION
#include "sp.h"
// 其他文件只包含头文件
#include "sp.h"
| 禁止 ❌ | 正确 ✅ |
|---|---|
malloc / calloc / realloc | sp_alloc() |
const char* | sp_str_t(ptr+len字符串) |
strcmp, strlen | sp_str_equal(), sp_str_len() |
printf | SP_LOG() |
memset(&obj, 0, sizeof(obj)) | SP_ZERO_INITIALIZE() |
裸for循环遍历数组 | sp_dyn_array_for() 或 sp_carr_for() |
// 有符号整数
s8 = int8_t // 8位
s16 = int16_t // 16位
s32 = int32_t // 32位
s64 = int64_t // 64位
// 无符号整数
u8 = uint8_t // 8位
u16 = uint16_t // 16位
u32 = uint32_t // 32位
u64 = uint64_t // 64位
// 浮点数
f32 = float // 32位
f64 = double // 64位
// 字符
c8 = char // UTF-8字符
// sp_str_t = { char* data; u32 len; }
// 不需要null终止!长度是已知的
typedef struct {
const c8* data;
u32 len;
} sp_str_t;
// 创建字符串
sp_str_t s1 = sp_str_lit("hello"); // 编译时常量,零分配
sp_str_t s2 = sp_str_view(c_string); // 从C字符串创建视图(计算长度)
sp_str_t s3 = sp_str_from_cstr(c_str); // 分配并复制
// 比较(不能用strcmp!)
bool equal = sp_str_equal(s1, s2);
bool starts_with = sp_str_starts_with(s1, sp_str_lit("he"));
// 检查空字符串(不能用len > 0)
bool is_empty = sp_str_empty(s1); // ✅ 正确
bool wrong = s1.len > 0; // ❌ 错误
// 所有分配都经过上下文分配器,自动初始化为零
void* ptr = sp_alloc(1024); // 分配1024字节,已清零
// 数组分配
u32* numbers = sp_alloc(sizeof(u32) * 100);
// 无需手动free,使用上下文自动管理
// 或者在需要时使用:
sp_free(ptr);
// 总是零初始化
my_struct_t obj = SP_ZERO_INITIALIZE(); // ✅ 正确
my_struct_t obj = {0}; // C99替代
// 使用指定初始化器(C99)
config_t cfg = {
.port = 8080,
.max_connections = 100,
.name = sp_str_lit("my_server")
};
// 声明数组(stb风格)
sp_dyn_array(int) numbers = SP_NULLPTR;
// 添加元素
sp_dyn_array_push(numbers, 42);
sp_dyn_array_push(numbers, 100);
// 访问
int first = numbers[0];
u32 count = sp_dyn_array_size(numbers);
u32 capacity = sp_dyn_array_capacity(numbers);
// 遍历(不要用裸for循环)
sp_dyn_array_for(numbers, i) {
SP_LOG("numbers[{}] = {}", SP_FMT_U32(i), SP_FMT_S32(numbers[i]));
}
// 清理(如果使用上下文分配器,通常不需要)
sp_dyn_array_free(numbers);
// 简写形式
sp_da(int) numbers = SP_NULLPTR; // sp_da = sp_dyn_array
// 所有操作宏
sp_dyn_array_push(arr, value); // 添加元素
sp_dyn_array_pop(arr); // 弹出最后一个
sp_dyn_array_back(arr); // 获取最后一个
sp_dyn_array_clear(arr); // 清空(不释放内存)
sp_dyn_array_free(arr); // 释放内存
sp_dyn_array_size(arr); // 获取元素数量
sp_dyn_array_capacity(arr); // 获取容量
sp_dyn_array_reserve(arr, n); // 预留容量
sp_dyn_array_resize(arr, n); // 调整大小
// 声明哈希表:key类型, value类型
sp_ht(sp_str_t, s32) scores = SP_NULLPTR;
// 设置自定义哈希和比较函数(用于字符串键)
sp_ht_set_fns(scores, sp_ht_hash_str, sp_ht_compare_str);
// 插入
sp_ht_insert(scores, sp_str_lit("alice"), 100);
sp_ht_insert(scores, sp_str_lit("bob"), 85);
// 查找
s32* score = sp_ht_getp(scores, sp_str_lit("alice"));
if (score) {
SP_LOG("Alice's score: {}", SP_FMT_S32(*score));
}
// 检查键是否存在
bool has_bob = sp_ht_key_exists(scores, sp_str_lit("bob"));
// 遍历
sp_ht_for(scores, it) {
sp_str_t* name = sp_ht_it_getkp(scores, it); // 获取键指针
s32* score = sp_ht_it_getp(scores, it); // 获取值指针
SP_LOG("{}: {}", SP_FMT_STR(*name), SP_FMT_S32(*score));
}
// 替代 printf,支持类型安全和颜色
SP_LOG("Hello, {}!", SP_FMT_CSTR("world"));
// 数字格式化
s32 num = 42;
SP_LOG("The answer is {}", SP_FMT_S32(num));
// 字符串格式化
sp_str_t name = sp_str_lit("Alice");
SP_LOG("Hello, {}", SP_FMT_STR(name));
// 颜色支持
SP_LOG("{:fg green}Success!{:reset}", SP_FMT_CSTR(""));
SP_LOG("{:fg red}Error:{:reset} {}", SP_FMT_CSTR(""), SP_FMT_CSTR("something went wrong"));
// 可用颜色: black, red, green, yellow, blue, magenta, cyan, white
// 加bright前缀: bright-red, bright-green, 等等
// 格式化到字符串
sp_str_t message = sp_format("Hello, {}!", SP_FMT_CSTR("world"));
// 使用完后释放(如果需要)
sp_free(message.data);
| 宏 | 用途 |
|---|---|
SP_FMT_S32(val) | s32整数 |
SP_FMT_U32(val) | u32整数 |
SP_FMT_S64(val) | s64整数 |
SP_FMT_U64(val) | u64整数 |
SP_FMT_F32(val) | f32浮点 |
SP_FMT_F64(val) | f64浮点 |
SP_FMT_CSTR(val) | C字符串 (const char*) |
SP_FMT_STR(val) | sp_str_t字符串 |
SP_FMT_BOOL(val) | bool |
SP_FMT_CHAR(val) | 字符 |
SP_FMT_PTR(val) | 指针 |
// 总是处理所有情况,使用花括号
switch (state) {
case STATE_IDLE: {
// 处理空闲状态
break;
}
case STATE_RUNNING: {
// 处理运行状态
break;
}
case STATE_STOPPED: {
// 处理停止状态
break;
}
default: {
SP_UNREACHABLE_CASE(); // 捕获未处理的情况
}
}
// 如果需要fallthrough,显式标记
switch (value) {
case 0: {
// 处理0
sp_fallthrough(); // 显式fallthrough
}
case 1: {
// 处理0和1
break;
}
}
// 对于可恢复错误,返回错误枚举
typedef enum {
ERR_OK = 0,
ERR_NOT_FOUND,
ERR_INVALID_INPUT,
ERR_OUT_OF_MEMORY,
} err_t;
err_t load_config(sp_str_t path, config_t* out_config) {
if (sp_str_empty(path)) {
return ERR_INVALID_INPUT;
}
if (!sp_os_path_exists(path)) {
SP_LOG("Config not found: {}", SP_FMT_STR(path));
return ERR_NOT_FOUND;
}
// ... 加载配置
return ERR_OK;
}
// 使用 SP_ASSERT 检查不变量
void process_items(item_t* items, u32 count) {
SP_ASSERT(items); // 检查非空
SP_ASSERT(count > 0); // 检查有效数量
// ... 处理
}
// SP_FATAL 用于不可恢复的错误
if (critical_failure) {
SP_FATAL("Cannot continue: {}", SP_FMT_STR(reason));
// 这会打印消息并终止程序
}
// sp_try - 传播错误
s32 result = some_operation();
sp_try(result); // 如果result != 0,返回result
// sp_try_as - 将错误映射为另一个
sp_try_as(failed_operation(), ERR_CUSTOM);
// sp_require - 要求条件为真
sp_require(ptr != NULL); // 如果失败,返回
sp_require_as(ptr != NULL, ERR); // 如果失败,返回ERR
// 路径操作
sp_str_t cwd = sp_os_get_cwd();
sp_str_t joined = sp_os_path_join(sp_str_lit("/home"), sp_str_lit("user"));
bool exists = sp_os_path_exists(path);
bool is_file = sp_os_path_is_file(path);
bool is_dir = sp_os_path_is_dir(path);
// 文件读写
sp_str_t content = sp_os_read_file(path);
sp_os_write_file(path, content);
sp_os_append_file(path, content);
// 目录操作
sp_os_mkdir(path);
sp_os_rmdir(path);
sp_dyn_array(sp_str_t) entries = sp_os_list_dir(path);
// 运行命令
sp_ps_result_t result = sp_ps_run(sp_str_lit("ls -la"));
if (result.status == 0) {
SP_LOG("Output: {}", SP_FMT_STR(result.stdout));
}
// 启动子进程
sp_ps_t* child = sp_ps_spawn(sp_str_lit("./my_program"));
sp_ps_wait(child);
// 数组长度(编译时)
int arr[] = {1, 2, 3, 4, 5};
u32 len = sp_carr_len(arr); // = 5
// 数组遍历
sp_carr_for(arr, i) {
SP_LOG("arr[{}] = {}", SP_FMT_U32(i), SP_FMT_S32(arr[i]));
}
// 最大值/最小值
s32 max = sp_max(a, b);
s32 min = sp_min(a, b);
// 交换
sp_swap(s32, a, b);
// 对齐
void* aligned = sp_align_up(ptr, 16);
#define SP_IMPLEMENTATION
#include "sp.h"
typedef struct {
sp_str_t name;
s32 score;
} player_t;
int main(void) {
// 零初始化
sp_dyn_array(player_t) players = SP_NULLPTR;
// 添加玩家
player_t alice = {
.name = sp_str_lit("Alice"),
.score = 100
};
sp_dyn_array_push(players, alice);
player_t bob = {
.name = sp_str_lit("Bob"),
.score = 85
};
sp_dyn_array_push(players, bob);
// 使用彩色日志输出
SP_LOG("{:fg cyan}Player Scores:{:reset}", SP_FMT_CSTR(""));
sp_dyn_array_for(players, i) {
player_t* p = &players[i];
SP_LOG(" {}: {:fg green}{}",
SP_FMT_STR(p->name),
SP_FMT_S32(p->score));
}
// 计算平均分
s32 total = 0;
sp_dyn_array_for(players, i) {
total += players[i].score;
}
f32 average = (f32)total / sp_dyn_array_size(players);
SP_LOG("Average: {:fg yellow}{:.2f}",
SP_FMT_F32(average));
return 0;
}
在提交代码前,确认:
SP_ZERO_INITIALIZE() 初始化所有结构体sp_str_t 而不是 const char*sp_alloc() 而不是 malloc()SP_LOG() 而不是 printf()sp_str_empty() 而不是检查 len > 0sp_dyn_array_for() 或 sp_carr_for() 遍历数组sp_str_equal() 而不是 strcmp()// ❌ 错误: 使用C字符串
const char* name = "Alice";
printf("Hello %s\n", name);
// ✅ 正确: 使用sp_str_t
sp_str_t name = sp_str_lit("Alice");
SP_LOG("Hello {}", SP_FMT_STR(name));
// ❌ 错误: 手动计算字符串长度
if (strlen(str) > 0) { ... }
// ✅ 正确: 使用sp.h的API
if (!sp_str_empty(str)) { ... }
// ❌ 错误: 裸malloc
int* arr = malloc(sizeof(int) * 10);
// ✅ 正确: 使用sp_alloc
int* arr = sp_alloc(sizeof(int) * 10);
// ❌ 错误: 手动for循环
for (u32 i = 0; i < sp_dyn_array_size(arr); i++) { ... }
// ✅ 正确: 使用遍历宏
sp_dyn_array_for(arr, i) { ... }
以下是在 TED (Termux Editor) 项目中应用 sp.h 的经验总结:
sp.h 是单头文件库,需要在一个且仅一个 C 文件中定义 SP_IMPLEMENTATION 宏:
// 在 main.c 中:
#define SP_IMPLEMENTATION
#include "sp.h"
// 在其他文件中只需包含头文件:
#include "sp.h"
错误现象:多个 .o 文件中出现重复定义的链接错误。
解决方案:确保 SP_IMPLEMENTATION 只在主源文件中定义一次。
在 Android/Termux 环境中,某些 POSIX 函数不可用:
posix_spawn_file_actions_addchdir_np 在 Android 上缺失
解决方案:在编译时添加 -DSP_PS_DISABLE 禁用进程支持模块:CFLAGS += -DSP_PS_DISABLE
sp.h 的 API 命名有特定规则,常见错误包括:
sp_str_eq → 正确:sp_str_equalsp_cstr_eq_n → 正确:strncmp(标准库函数)sp_os_read_entire_file → 正确:sp_io_read_filesp_str_builder_create → 正确:使用 sp_io_writer_from_dyn_mem() + sp_str_builder_from_writer()sp_str_t 结构使用 .data 成员,而不是 .ptr:
// ❌ 错误
sp_str_t str = ...;
c8 ch = str.ptr[i];
// ✅ 正确
c8 ch = str.data[i];
sp.h 没有提供 sp_io_stdout() 函数:
// ✅ 正确方式
sp_io_writer_t stdout_writer = sp_io_writer_from_fd(
STDOUT_FILENO,
SP_IO_CLOSE_MODE_NONE
);
sp_io_write_str(stdout_writer, text);
SP_ZERO_INITIALIZE() 不能用于赋值语句:
// ❌ 错误(全局变量)
editor_t E = SP_ZERO_INITIALIZE();
// ✅ 正确方式
editor_t E;
sp_memset(&E, 0, sizeof(E));
使用 isalpha、isdigit 等函数时需要包含 <ctype.h>:
#include <ctype.h> // 必须包含
sp_str_lit() 宏用于字符串字面量,不适用于单个字符:
// ❌ 错误
sp_str_t ch = sp_str_lit("a"); // 实际上是字符串 "a"
// ✅ 对于单个字符操作,使用字符类型 c8
c8 ch = 'a';
文件操作应使用 sp_io_* 系列函数:
sp_str_t content = sp_io_read_file(path);sp_io_writer_from_file() + sp_io_write_str() + sp_io_writer_close()reference/index.md 查找正确的函数签名这些经验来自 TED 编辑器的实际开发过程,希望能帮助其他开发者更顺利地在项目中使用 sp.h 库。