【C/C++】从 setjmp 到 ucontext 再到 hook:C 语言协程是怎么跑起来的?

LVGL v8.3 图片显示与解码缓存实战:PNG 上屏、C 数组与文件双方案 LVGL v8.3 图片两种存储方式、PNG/JPG 上屏解码器挂载、LV_IMG_CACHE_DEF_SIZE 缓存调优、手动预解码头像有效性判断,及"缓存被动画清空"真实踩坑。 阅读详情

【C/C++】从 setjmp 到 ucontext 再到 hook:C 语言协程是怎么跑起来的?

在这里插入图片描述

1. 为什么需要协程?

网络服务里经常有一个矛盾:

  • 同步阻塞代码好写:send() 之后 recv(),业务逻辑顺着往下走;
  • 异步事件模型性能好:epoll_wait() 拿到事件,再按 fd 分发处理;
  • 但纯异步代码容易变成回调、状态机、上下文传递,一旦多个请求之间有依赖,代码很快变复杂。

课程笔记里对这个问题的概括很直接:

func() {
    async_send();
    async_recv();
}

async_xxx() {
    if (1 == poll(fd, 0)) {
        switch();  // 判断 IO,然后切换出去
    }
}

协程要解决的就是这个问题:业务代码看起来像同步代码,底层遇到 IO 不就绪时主动让出 CPU;等 fd 就绪后,调度器再恢复它。

所以协程不是为了“炫技式切栈”,而是为高并发 IO 服务:

  • 写法上接近同步;
  • 执行上接近异步;
  • 调度发生在用户态,切换成本比线程更轻;
  • 和 epoll 组合后,可以用少量线程管理大量连接。

2. 协程的本质:保存现场,再恢复现场

一个函数普通调用时,执行权从调用者进入被调用者;函数返回后,栈帧销毁,不能随便回到中间某一行继续执行。

协程不一样。它要支持两个基本动作:

  • yield:当前协程主动让出执行权,保存自己的执行现场;
  • resume:调度器恢复某个协程,让它从上次暂停的位置继续跑。

课程里列了三种常见实现路线:

1. setjmp / longjmp
2. ucontext
3. asm 汇编

这三种方法的核心都一样:保存 CPU 寄存器、栈信息和下一条执行位置;恢复时再把这些状态装回去。

3. 第一站:setjmp / longjmp 认识“跳回保存点”

先看最小示例 jmp.c:

#include <setjmp.h>
#include <stdio.h>

jmp_buf env;

void func(int val)
{
    printf("In func, about to longjmp: %d\n", val);
    longjmp(env, ++val);
}

int main()
{
    int ret = setjmp(env);
    if (ret == 0) {
        func(ret);
    } else if (ret == 1) {
        func(ret);
    } else if (ret == 2) {
        func(ret);
    } else if (ret == 3) {
        func(ret);
    }
}

setjmp(env) 做了两件事:

  1. 第一次调用时,保存当前执行现场,然后返回 0;
  2. 后续如果有人 longjmp(env, value),程序会跳回这个保存点,并让 setjmp 返回 value。

运行结果类似:

In func, about to longjmp: 0
In func, about to longjmp: 1
In func, about to longjmp: 2
In func, about to longjmp: 3

这已经有一点“切换”的味道了:程序不是按普通调用栈返回,而是跳回了之前保存的点。

但 setjmp/longjmp 还不够像完整协程,因为它默认没有给每个协程准备独立栈。真正的协程需要多个执行流各自拥有自己的栈,否则很难让多个任务都停在自己的调用链中间。

4. 第二站:ucontext 做出真正的用户态切换

ucontext.c 就是一个非常适合入门的协程雏形。它创建了三个上下文 ctx[0]、ctx[1]、ctx[2],再用 main_ctx 充当最小调度器。

在这里插入图片描述

关键初始化代码如下:

ucontext_t ctx[3];
ucontext_t main_ctx;

getcontext(&ctx[0]);

char *stack1 = malloc(SIGSTKSZ);
ctx[0].uc_stack.ss_sp = stack1;
ctx[0].uc_stack.ss_size = SIGSTKSZ;
ctx[0].uc_link = &main_ctx;
makecontext(&ctx[0], func1, 0);

这里有四个关键点:

  • getcontext(&ctx[0]):初始化一个上下文结构;
  • uc_stack:给这个上下文配置独立栈;
  • uc_link = &main_ctx:当 func1 执行结束时回到哪里;
  • makecontext(&ctx[0], func1, 0):指定这个上下文第一次被恢复时执行 func1。

协程函数里主动切回 main_ctx:

void func1()
{
    while (count++ < 30) {
        printf("In func1\n");
        swapcontext(&ctx[0], &main_ctx);
        printf("Back in func1\n");
    }
}

调度器再按顺序恢复某个协程:

while (count < 30) {
    printf("In main\n");
    swapcontext(&main_ctx, &ctx[count % 3]);
    printf("Back in main\n");
}

这两句 swapcontext 分别对应:

  • swapcontext(&ctx[0], &main_ctx):当前协程 yield,保存 ctx[0],切回调度器;
  • swapcontext(&main_ctx, &ctx[i]):调度器 resume 某个协程,从它上次暂停的位置继续。

我在 Linux/WSL 下重新编译运行过,输出开头如下:

In main
In func1
Back in main
In main
In func2
Back in main
In main
In func3
Back in main
In main
Back in func1
In func1

注意 Back in func1:它不是重新调用 func1,而是从上一次 swapcontext(&ctx[0], &main_ctx) 后面继续执行。这就是协程“暂停后恢复”的直观证据。

5. 从 demo 到协程库:需要 coroutine 和 scheduler

上面的 ucontext.c 只有三个固定函数,还不是完整协程库。课程笔记里给出了更接近工程实现的抽象。

协程对象大致需要保存:

struct coroutine {
    int fd;
    ucontext_t ctx; // stack, stack_size, func
    void *arg;

    queue_node(coroutine, ) ready_queue;
    rbtree_node(coroutine, ) wait_rb;
    rbtree_node(coroutine, ) sleep_rb;
};

调度器则需要管理:

struct scheduler {
    int epfd;
    struct epoll_event events[];

    queue_node(coroutine, ) ready_head;
    rbtree_root(coroutine, ) wait;
    rbtree_root(coroutine, ) sleep;
};

这两个结构说明了一件事:协程库不是只有“切换上下文”这么简单。真正跑起来时,调度器至少要维护三类协程:

  • ready:已经可以运行,等待被恢复;
  • wait:正在等待某个 fd 的 IO 事件;
  • sleep:等待定时器到期。

一个典型调度循环可以理解成这样:

while (1) {
    // 1. 运行 ready 队列里的协程
    while (!queue_empty(ready)) {
        co = pop_ready();
        resume(co);
    }

    // 2. 计算最近的 sleep 超时时间
    timeout = nearest_timer_timeout();

    // 3. 等待 IO 事件
    n = epoll_wait(epfd, events, maxevents, timeout);

    // 4. fd 就绪:把等待该 fd 的协程放回 ready
    for (i = 0; i < n; i++) {
        co = events[i].data.ptr;
        push_ready(co);
    }

    // 5. 定时器到期:sleep 协程也放回 ready
    expire_sleep_coroutines();
}

这样,yield/resume 就和 epoll_wait 接上了:协程不是随机切换,而是根据“可运行、IO 就绪、定时器到期”来调度。

6. 第三站:hook,把阻塞 IO 变成可调度 IO

如果业务代码里写的是:

while (1) {
    recv(fd, buffer, sizeof(buffer), 0);
    parser(buffer);
    send(fd, response, response_len, 0);
}

问题来了:recv 如果真的阻塞,整个线程都会卡住,调度器也没机会运行其他协程。

所以协程库通常会 hook 常见阻塞调用,例如:

read / write / recv / send / accept / connect / sleep

目录下的 hook.c 是一个最小 hook 示例,它用 dlsym(RTLD_NEXT, "...") 找到真正的 libc 函数:

#define _GNU_SOURCE
#include <dlfcn.h>

typedef ssize_t (*read_t)(int fd, void *buf, size_t count);
read_t read_f = NULL;

typedef ssize_t (*write_t)(int fd, const void *buf, size_t count);
write_t write_f = NULL;

ssize_t read(int fd, void *buf, size_t count)
{
    ssize_t ret = read_f(fd, buf, count);
    return ret;
}

ssize_t write(int fd, const void *buf, size_t count)
{
    return write_f(fd, buf, count);
}

void init_hook()
{
    read_f = dlsym(RTLD_NEXT, "read");
    write_f = dlsym(RTLD_NEXT, "write");
}

这段代码本身还没有调度逻辑,但它证明了一个关键机制:我们可以拦截业务代码里的 read/write,在自己的函数里决定什么时候调用真实系统调用。

真正接入协程调度后,hook read 的逻辑可以画成这样:

在这里插入图片描述

伪代码如下:

ssize_t read(int fd, void *buf, size_t count)
{
    struct pollfd pfd = {
        .fd = fd,
        .events = POLLIN,
    };

    int ready = poll(&pfd, 1, 0);
    if (ready <= 0) {
        // fd 暂时不可读,把当前协程挂到 wait 结构里
        epoll_ctl(scheduler->epfd, EPOLL_CTL_ADD, fd, &ev);

        // 当前协程 yield,调度器去跑别的协程
        coroutine_yield();
    }

    // 被 resume 回来时,fd 已经可读,再调用真实 read
    return read_f(fd, buf, count);
}

这就是“同步写法、异步执行”的核心。

业务层仍然写:

n = read(fd, buf, sizeof(buf));

但底层实际发生的是:

  1. fd 可读:直接调用真实 read_f;
  2. fd 不可读:注册到 epoll,当前协程 yield;
  3. epoll_wait 发现 fd 就绪;
  4. 调度器把对应协程放回 ready 队列;
  5. 协程 resume,再次进入 hook,最终完成真实读取。

7. 异步 DNS 和 epoll 服务端:协程要解决的真实场景

扩展目录里还有两个很有代表性的文件。

async_dns_client_noblock.c 展示了“纯异步”的写法:创建非阻塞 UDP socket,发送 DNS 请求,把 fd 注册到 epoll,等响应回来后回调处理结果。

核心结构很简单:

struct async_context {
    int epfd;
};

struct ep_arg {
    int sockfd;
    async_result_cb cb;
};

事件线程里等待 DNS 响应:

int nready = epoll_wait(epfd, events, ASYNC_CLIENT_NUM, -1);
for (i = 0; i < nready; i++) {
    struct ep_arg *data = (struct ep_arg*)events[i].data.ptr;
    int sockfd = data->sockfd;

    int n = recvfrom(sockfd, buffer, sizeof(buffer), 0,
                     (struct sockaddr*)&addr, (socklen_t*)&addr_len);

    struct dns_item *domain_list = NULL;
    int count = dns_parse_response(buffer, &domain_list);

    data->cb(domain_list, count);
}

这种写法性能很好,但业务逻辑会被拆成“提交请求”和“回调处理结果”。协程库想做的,就是把这种事件驱动能力藏到 hook 和 scheduler 下面,让上层代码重新变回顺序逻辑。

server_mulport_epoll.c 则展示了高并发网络服务常见结构:

  • 多端口监听;
  • epoll_wait 接收大量连接事件;
  • 客户端 fd 设置非阻塞;
  • 事件到来后可以直接处理,也可以投递到线程池。

课程截图里可以看到大量客户端回显输出:

在这里插入图片描述

这类场景正是协程库的用武之地:每个连接可以对应一个协程,业务代码像处理单连接一样顺序执行;调度器在背后负责把不可读、不可写、睡眠中的协程挂起。

8. 把整条链路串起来

到这里,可以把协程实现拆成四层:

第 1 层:上下文切换
setjmp/longjmp、ucontext、汇编保存和恢复 CPU/栈状态。

第 2 层:协程对象
每个 coroutine 保存 ctx、stack、入口函数、参数、状态。

第 3 层:调度器
维护 ready / wait / sleep,使用 epoll_wait 驱动 IO 协程恢复。

第 4 层:系统调用 hook
拦截 read/write/recv/send/sleep,把阻塞点改造成 yield 点。

执行流程可以概括为:

create coroutine
    -> makecontext / 初始化栈
    -> 放入 ready 队列
    -> scheduler resume
    -> 业务代码执行
    -> 遇到 IO 不就绪
    -> hook 注册 epoll 并 yield
    -> scheduler 跑其他协程
    -> epoll_wait 返回事件
    -> 对应协程回到 ready
    -> resume 后继续执行

这也是 ntyco、libco、go runtime 等协程/轻量线程系统的共同味道:把“等待”从线程阻塞变成任务挂起,把“恢复”交给调度器。

9. 编译运行本文示例

这些示例依赖 Linux API,建议在 Linux 或 WSL 下运行。

gcc -Wall -Wextra -O0 -g -o jmp jmp.c
./jmp

gcc -Wall -Wextra -O0 -g -o ucontext ucontext.c
./ucontext

gcc -Wall -Wextra -O0 -g -o hook hook.c -ldl
./hook

我本地验证到的 hook 输出为:

buffer: 1234567890

这说明顶层 hook.c 已经成功通过自定义 read/write 包装函数调用到了真实 libc 系统调用。

10. 小结

协程可以用一句话理解:

协程是在用户态保存和恢复执行现场,并把 IO 等待交给调度器管理的一种并发执行单元。

本文从目录里的代码出发,走了一条从浅到深的路线:

  • jmp.c:理解“跳回保存点”;
  • ucontext.c:理解“独立栈 + yield/resume”;
  • hook.c:理解“拦截系统调用”;
  • server_mulport_epoll.c / async_dns_client_noblock.c:理解协程为什么适合高并发 IO。

如果继续往下实现一个完整协程库,下一步就可以补齐:

  • coroutine_create():分配协程对象和栈;
  • coroutine_yield() / coroutine_resume():封装上下文切换;
  • scheduler_run():实现 ready/wait/sleep 三类任务调度;
  • read/recv/send/sleep hook:把阻塞点接入 epoll;
  • 多线程多核模式:每个线程一个 scheduler,连接按线程分片。

做到这里,一个“看起来同步、跑起来异步”的 C 协程网络库就有了骨架。

学习链接: https://github.com/0voice

全面解析智能座舱芯片参数:高通 (SA8295)vs 联发科,真实案例一行行讲给你听! 使用场景推荐芯片中低端车型 + 1屏导航语音SA8155P / MT8628(性价比高)中控+副驾+仪表+全景影像SA8255P / MT8678(性能强)高阶自动驾驶+域控融合SA8295P(顶级安全+算力) 阅读详情

相关推荐

迪文DGUSII串口屏中文显示实战:手把手教你制作16x16点阵字库

本文提供了一份迪文DGUSII串口屏16x16点阵中文字库制作的完整实战指南。从显示原理、字体选择与参数设置,到使用FontLib Make工具生成字库文件,再到部署、调用及存储空间优化,手把手教你解决中文显示难题,确保界面文字既美观又稳定。

weixin_29190573的博客 154

c/c++ coroutine

1.实现机制1.1. switch-case Protothread 最轻,但受限最大1.2. setjmp/longjmp StateThread采用这种方式1.3. ucontext1.4. boost::context针对不同的arch用汇编实现的http://www.boost.org/doc/libs/1_59_0/libs/context/doc/html/

cadem的专栏 1656

关于动态存储器dram中rank,bank,device之间的关系

DEVICE_WIDTH表示设备的带宽(单位bit),每个列(column)包含DEVICE_WIDTH 位(bit),每一行(row)包含NUM_COLS个column。   而每一个bank包含NUM_ROWS个行(row)。所以一个设备(device)的存储容量为PER_DEVICE_STORAGE =NUM_ROWS*NUM_COLS*DEVICE_WIDTH*NUM_BANKS (i

wyj7260的专栏 4731

static 的三种用法:C 语言里一个关键字的三张面孔

本文系统讲解 C 语言中 static 关键字的三种用法:修饰局部变量延长生命周期、修饰全局变量与函数限制作用域到本文件。通过一个包含三个文件的完整工程示例对照演示,并从存储期、作用域与链接性三个属性解释其统一原理,最后补充单元测试框架偏爱 static 函数的工程实践。

大模型全栈工程师 · Thanks_ks 313

自动权重化图卷积网络(Auto-Weighted GCN)算法在反洗钱识别中的应用及代码实现

该算法通过自动学习节点之间的权重信息,能够更好地捕捉AML数据中的关系和结构,从而提高反洗钱识别的准确性和效果。传统的AML识别方法通常依赖于专家定义的规则和手动提取的特征,但这些方法在应对大规模和复杂的数据时存在一定的局限性。GCN通过在图上执行卷积操作来捕捉节点之间的关系和结构信息,从而实现对图数据的学习和表示。自动权重化图卷积网络是一种基于GCN的改进算法,能够自动学习节点之间的权重信息,从而更好地捕捉图中的关系。利用自动计算的权重值执行图卷积操作。根据节点之间的相似度,计算每个节点的权重。

syntax_api860的博客 376

用playwright封装一个处理web网页的爬虫,并隐藏自动化特征,自动处理反爬

本文介绍了一个使用Playwright封装的Python爬虫函数,具备反爬对抗能力。该脚本通过移除自动化标志、注入真实用户代理、禁用Chrome自动化属性等措施,有效绕过电商、新闻等中等反爬网站的检测。核心功能包括:隐藏navigator.webdriver标志、模拟人类浏览行为、设置合理的设备参数,并支持代理和延迟加载。使用时需注意避免高频访问并定期更新User-Agent。该方案适用于需要JavaScript渲染的动态网页,性能优于非无头模式。

数据知道的博客 3393

深挖C++:类和对象(中)

(先进后出)

xys_678的博客 239

YOLO11实战优化:长尾分布解决 | 引入Seesaw Loss配合YOLO11分类头,完美应对类别极其不平衡的现实业务场景

摘要: YOLO11在工业缺陷检测等长尾分布场景中面临尾部类别检测失效的问题,根源在于头部类别样本的负样本梯度压制了尾部类别的学习。传统方法如重采样、Focal Loss等效果有限。Seesaw Loss通过动态调整缓解因子和补偿因子,平衡类别间的梯度分布,结合YOLO11的解耦分类头、Anchor-Free设计和C2PSA/C3K2模块,显著提升尾部类别识别能力。文章详细解析了Seesaw Loss的数学原理,并提供了可直接集成到YOLO11的PyTorch实现代码,为长尾分布下的目标检测提供了实用解决方

2601_95807009的博客 252

选型笔记:Type‑C 扩展坞方案评估|DP1.4 转 HDMI2.0 桥接芯片 GSV2201S@ACP

本文记录 GSV2201S 这款 DP1.4a 转 HDMI2.0 桥接芯片评估,梳理视频、HDCP、PD 集成能力,总结 Type‑C 转接 Dongle、扩展坞方案的适配边界,以及差分走线、PD 时序、HDCP 握手等高频调试问题,为 C 口视频转接方案选型提供参考。

ACP广源盛的博客 221

unity6000.0.3版本URP下ScriptableRendererFeature如何实现一张自定义贴图效果?

🏆本文收录于 《全栈 Bug 调优(实战版)》 专栏。专栏聚焦真实项目中的各类疑难 Bug,从成因剖析 → 排查路径 → 解决方案 → 预防优化全链路拆解,形成一套可复用、可沉淀的实战知识体系。无论你是初入职场的开发者,还是负责复杂项目的资深工程师,都可以在这里构建一套属于自己的「问题诊断与性能调优」方法论,助你稳步进阶、放大技术价值。

**My Coding Family** 584

openharmony之启动恢复子系统详解

需求: 添加一个名为my_service的自定义服务步骤1: 创建服务配置],],"env": [步骤2: 添加到主配置],"jobs": ["cmds": [步骤3: 验证配置# ✅ 检查配置格式 jq . /system/etc/init/my_service.cfg # 🔄 重新加载配置 begetctl reload # 📊 验证服务状态 begetctl dump_service my_serviceOpenHarmony init进程的完整生命周期。🔄 三阶段启动模型⚙️ 配置体系。

m0_67505153的博客 600

C++移动构造函数详解

移动构造函数是C++11引入的一种特殊构造函数,用于从临时对象(右值)"窃取"资源来初始化新对象,而不是进行昂贵的深拷贝。char* data;public:// 移动构造函数:参数是右值引用 &&// 关键:把源对象的资源置空。

拒绝AI文,努力在AI时代写出感动人心的好文章 391

基于LSTM算法实现交通流量预测(Pytorch版)

本研究采用PyTorch框架,设计并实现了一种基于LSTM(Long Short-Term Memory)网络的交通流量预测模型。针对时序性显著的交通流量数据,模型首先将多节点、多通道的时空信息整合为一维特征输入至多层LSTM单元,利用其捕捉长期依赖及短期波动的能力进行深度学习。全连接层随后调整时间步长以适应预测周期需求。模型结构灵活,能适应不同节点数、输入输出长度及通道配置,实现在给定历史流量数据基础上对未来交通流量的精准预测,为交通管理与规划提供数据驱动支持。

打酱油的葫芦娃 4638

ThunderKittens 深度解析:一个不基于 MLIR 的 C++ 模板 GPU DSL

Triton 是"让更多人能写 GPU kernel",ThunderKittens 是"让会写的人写得更爽"。前者用 MLIR 换易用性,后者用 C++ 模板换可控性。它们不是替代关系,而是覆盖了 GPU 编程光谱的两端。ThunderKittens 的出现,本质上是对"GPU DSL 必须基于 MLIR"这一默认假设的挑战。编译期类型系统本身就可以是一个强大的 DSL 基础设施,不需要中间 IR,不需要黑盒 pass,也能实现 tile 级抽象和零运行时开销。

Maxwell_Newton的博客 336

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

【C语言】字符与字符串函数

C语言中字符串为以\0结尾的字符数组,标准库函数基于此约定。本文解析字符串内存布局,详解ctype.h与string.h中字符分类、长度计算、拷贝、比较等函数的使用要点,强调长度判断与越界风险,最后通过命令行待办事项示例整合fgets、sscanf、strncpy、strcmp,展示实际应用中的关键细节。

记录在学习过程中学到的知识 343

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

C++ 多态详解:作用、原理与实战用法

多态是C++面向对象的核心特性,通过虚函数实现运行期动态绑定,使同一接口在不同对象上表现出不同行为。其核心作用包括统一接口、提升可扩展性、降低耦合、支持运行时分派与批量处理。实现依赖虚函数表(vtable)和虚指针(vptr),需注意虚析构函数、对象切片等陷阱。相比编译期多态(模板),运行期多态灵活性高但有调用开销。合理结合两者,可兼顾代码的灵活性与性能,是构建可扩展、低耦合系统的关键。

2402_85743155的博客 382

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

C++ 函数模版

模板参数:● template ,T 就是类型占位符,名字可以自定义,习惯用T● 占位符可以在函数返回值、形参、函数内部当作类型使用使用要点模板代码一般写在头文件:编译器需要看到完整模板源码,才能在每个编译单元完成实例化全局函数、类内成员函数都可以写成模板实例化发生在编译阶段,不是运行时隐式实例化:调用时编译器自动推导实参类型,生成对应版本函数。

2501_94041724的博客 304

C++ 运行时多态与虚函数机制全面剖析【面试重点】

构成C++ 运行时多态必须是基类的指针或者引用调用虚函数。被调用的函数必须是虚函数,并且派生类必须对该虚函数完成重写/覆盖(Override)。只有基类的指针或引用才能既指向基类对象,又指向派生类对象(切片机制);而只有完成了虚函数重写,派生类与基类才具备不同的函数行为,达到“指向谁就调用谁”的效果。在类成员函数的前面加上virtual关键字修饰,该成员函数就成为了虚函数。public:// 成员函数前加 virtual 修饰,声明为虚函数。

Fuyo_0209的博客 437

#SQLite 数据库笔记一:(概念 / 终端指令 / SQL 语句 / C 编程 / 回调函数)

数据库 = 数据的仓库,假设数据是海量。能力说明存储数据持久化保存,程序退出数据还在统计数据、分组、排序数据管理增、删、改、查(CRUD)嵌入式设备上也会用数据库(单片机网关、路由器、工业 HMI 都常见),因为 sqlite 体积小、无需服务进程、单文件即可。数据库 = 库 > 表 > 记录(行) > 字段(列)sqlite3 = 开源、C 写的、单文件、免安装、最多 2T 的嵌入式关系型数据库。

lwq20051110的博客 204

【C++】【STL】string_OJ题

本文总结了9道C++ STL中关于string的OJ题目,按操作类型分为三组:反转与就地修改(如917、541、151)、计数与查找(如387、125、58)、模拟与大数运算(如415、43、8)。核心技巧包括双指针收敛、字符分类处理、进位控制及边界值处理。所有题目使用同一套源码,在标准库std::string与自定义容器MyString间切换仅需宏定义,验证结果一致性。代码通过随机测试用例与Trace函数逐行比对,确保逻辑正确性,强调接口对齐的重要性。

这里是Cocatrice的博客,感谢大家一直以来的支持和鼓励,如果对C/C++方向感兴趣的话,可以通过主页的微信名片加Cocatrice的好友,也可以通过邮箱:Chen31980094@163.com 联系,一同加油! 4371

C 语言 malloc() 详解与实战 Demo

本文详解C语言malloc()函数,涵盖其语法、内存原理及实战应用。通过动态整数、数组、结构体与链表的Demo,演示如何安全申请、使用与释放内存,强调避免内存泄漏与空指针检查的重要性,帮助开发者掌握动态内存管理核心技能。

tangPHP的博客 370

【C++ 基础入门】类和对象(上):封装、对象模型与 this 指针

C++类与对象知识(面向过程和面向对象的初步认识,类的初步认识,类的访问限定符及封装,类的作用域、实例化,类的对象模型,this指针)

2601_96587588的博客 880

【C++ 基础入门】C++继承(下):问题、底层原理与组合

C++继承(菱形继承的问题、虚继承原理、继承与组合、构造顺序)

2601_96587588的博客 773

C++09_vector类复习

本文以10个核心问题为主线,系统解析C++ vector的底层机制:从基本概念、初始化方式、遍历方法,到size与capacity的区别、扩容策略(VS 1.5×,g++ 2×)、reserve与resize差异、中间增删的挪动原理、迭代器失效原因,直至二维vector的内存布局(含三指针结构)。通过“先会用、再懂扩容、最后看清对象结构”的递进逻辑,揭示vector作为动态数组的本质——连续内存、自动扩容、随机访问高效,但插入删除代价高,迭代器易失效。全文深入浅出,助读者摆脱“黑盒”认知,真正掌握其内存图景

lemo_n_的博客 337

cargo bench 快速上手

是 Rust 内置的基准测试命令,用于。不过,Rust 标准库的#[bench]属性目前仅在版本可用,因此在稳定版(stable)上,社区标准做法是使用框架。

Doublelv 的博客专栏。 339

C++零基础入门第3周:if/else、switch、for/while/do-while 控制流完全指南(附20段可运行代码+避坑清单)

第3周 · 控制流1. 分支:if/else2. 分支:switch3. 循环:for4. 循环:while / do-while5. 范围 for(C++11)6. 跳转:break/continue/return/goto7. 现代 C++ 分支特性避坑:悬空 else / 浮点比较避坑:忘 break / 作用域避坑:边界 off-by-one避坑:do-while 至少执行一次避坑:遍历时修改容器避坑:goto 破坏结构if constexpr / if consteval本周项目:猜数字游戏 #m

qq_58662768的博客 213
上一篇: 【C/C++】从 POSIX Socket 到 TCP 生命周期:一文理解网络 IO 的核心原理
下一篇: 【C/C++】从 DPDK 收包到用户态 TCP/IP 协议栈:一个 ustack 示例拆解
SilentSlot
博客等级 码龄16年 44粉丝 68原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值