You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于setjmp实现类Haskell素数生成器的C语言方案问询

用C无数组、无动态分配实现Haskell风格惰性素数生成

原Haskell实现逻辑

先看你给出的Haskell惰性素数生成代码:

primes' :: [Int] -> [Int]
primes' (p : xs) = p : primes' (filter (\x -> x `rem` p /= 0) xs)

primes :: [Int]
primes = primes' [2 ..]

这是增量式埃拉托斯特尼筛法的惰性实现:每次取列表首元素作为素数p,过滤掉后续所有能被p整除的数,再递归处理剩余列表。Haskell的惰性求值会把每个filter的过滤条件(也就是当前素数p)保存在调用栈/闭包中,按需生成下一个素数。


C语言实现方案(无数组、无动态分配)

用setjmp/longjmp模拟递归闭包与惰性求值

C没有原生闭包,但可以用setjmp/longjmp实现协程式的暂停/恢复,把每个过滤层的状态存在递归调用栈上,完全不需要数组或动态分配:

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

static int current_candidate = 2;
static jmp_buf return_point;

// 递归过滤函数:当前素数p,负责过滤所有不能被p整除的数
void filter(int p) {
    jmp_buf filter_jmp;
    // 保存当前过滤层的跳转点
    if (setjmp(filter_jmp) == 0) {
        // 首次进入,返回当前素数p给调用者
        longjmp(return_point, p);
    }
    // 持续寻找下一个不被p整除的候选数
    while (1) {
        current_candidate++;
        if (current_candidate % p != 0) {
            // 找到有效候选数,进入下一层过滤
            return_point = filter_jmp;
            filter(current_candidate);
        }
    }
}

// 获取下一个素数
int next_prime(void) {
    jmp_buf init_jmp;
    int result = setjmp(init_jmp);
    if (result == 0) {
        return_point = init_jmp;
        filter(current_candidate);
    }
    // 每次longjmp返回时,result就是生成的素数
    return result;
}

int main(void) {
    // 生成前15个素数
    for (int i = 0; i < 15; i++) {
        printf("%d ", next_prime());
    }
    return 0;
}

实现逻辑说明

  1. 每个filter调用对应Haskell中的一次primes'递归,p作为当前素数保存在栈上
  2. setjmp保存当前过滤层的执行状态,longjmp负责将生成的素数返回给上层调用者
  3. 所有状态(jmp_buf、p)都存储在递归调用栈上,完全没有动态分配或数组

但这个方案有个局限:递归栈深度有限。C的默认栈大小通常在几MB,每个filter调用会占用几十字节的栈空间,所以最多只能生成几千个素数,超过后会触发栈溢出。


关于「能否完全避免动态分配」的分析

你怀疑无法避免动态分配,这个结论是对的——如果要支持无限生成素数,C语言里确实无法完全避免动态分配:

  • Haskell的惰性列表本质是堆上分配的链表节点,每个filter都会生成新节点,只是自动内存管理让你没感知到
  • 在C中,若要生成无限多素数,每个新素数对应的过滤条件需要永久保存,但栈的大小是固定且有限的,无法容纳无限增长的过滤状态。只有用堆内存(malloc)才能动态扩展存储这些过滤条件的空间。

但如果只需要生成有限数量的素数,且数量在栈的承载范围内,上面的方案就可以完全避免动态分配,纯用栈内存实现。


数学分析:惰性筛法的特性

这个Haskell实现是增量式埃氏筛,和传统埃氏筛的区别:

  • 传统埃氏筛是预先标记数组中的非素数,时间复杂度O(n log log n),空间复杂度O(n)
  • 增量式惰性筛的时间复杂度同样是O(n log log n),但空间复杂度是O(k)(k为已生成的素数数量),且是按需分配空间,不会一次性占用大量内存
  • 每次生成新素数时,需要遍历所有已生成的素数进行取模检查,这和惰性筛的逻辑完全一致

内容的提问来源于stack exchange,提问作者Andrey

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 04:07:06