Skip to content

函数 ​

CIN 用 function 关键字定义函数,语法接近 Go/C 的混合体:类型写在参数名之前,返回类型写在 -> 之后。

定义语法 ​

text
function 函数名(参数表) -> 返回类型 {
    函数体
}
  • 参数表形如 int a, int b,类型在名字前面。
  • -> 返回类型 可省略,省略时返回类型为 void。
  • 函数体是花括号块;块内的换行是语句终止符,不是装饰。
  • 函数体可以完全为空:function noop() {}。
cin
function add(int a, int b) -> int {
    return a + b
}

// 无返回值: -> void 可省略
function greet() {
    println("hi")
}

function main() -> int {
    println(int_to_str(add(2, 3)))   // 5
    greet()                          // hi
    return add(2, 3)
}

顺序无关

同一文件内的函数可以先用后定义(例如 main 调用写在文件末尾的 helper),编译器做两遍编译,先扫签名再生成代码。详见下文两遍编译。

参数传递 ​

标量按值传递 ​

int / float / bool 参数按值传递:函数内修改形参不影响调用方的实参。

cin
function double_it(int x) -> int {
    x = x * 2          // 只改副本
    return x
}

function main() -> int {
    int n = 5
    int r = double_it(n)
    println(int_to_str(n) + " " + int_to_str(r))   // 5 10
    return r
}

数组与 struct 参数退化为引用 ​

数组参数(T[])与 struct 参数传递的是同一块存储,函数内修改对调用方可见。

cin
struct Point { int x }

function bump(int[] arr) {
    arr[0] = 42        // 调用方可见
}

function move(Point p) {
    p.x = 99           // 调用方可见
}

function main() -> int {
    int a[3]
    bump(a)
    println(int_to_str(a[0]))   // 42

    Point pt
    pt.x = 1
    move(pt)
    println(int_to_str(pt.x))   // 99
    return 0
}

注意与 struct 赋值语义的区别

struct 传参/取地址式使用是引用可见的,但 struct 整体赋值(P b = a)是值拷贝(b.x = 7 不影响 a.x)。两种语义并存,写代码时按“函数内改 struct 会改到外面”来理解即可。

固长数组参数与衰减 ​

固长数组作参数时,维度写在参数名之后:int a[3]、int m[2][3]。传入后统一衰减为指针形式(等价于 int[] / int[][]),因此数组大小不会随参数传递。

cin
function fill3(int a[3], int v) {
    for (int i = 0; i < 3; i = i + 1) {
        a[i] = v
    }
}

function setm(int m[2][3], int r, int c, int v) {
    m[r][c] = v
}

function main() -> int {
    int a[3]
    fill3(a, 7)
    println(int_to_str(a[2]))     // 7

    int m[2][3]
    setm(m, 1, 2, 5)
    println(int_to_str(m[1][2]))  // 5
    return 0
}

不支持的写法

function fill3(int[3] a) 这种把维度写在类型名上的写法不会被解析,会报 Expected IDENT but got LBRACKET ('[')。只有 T[](空括号)能紧跟在类型名后。

值传递 / 引用可见 对照 ​

cin
function f(int x) -> int { x = 1
    return x }

function main() -> int {
    int n = 9
    f(n)
    println(int_to_str(n))   // 9
    return 0
}

返回值 ​

  • return 表达式 立即结束函数并把值交给调用方。
  • void 函数可以写裸 return,也可以完全不写;不写时函数自然结束。
  • 非 void 函数不写 return 时会返回 x0 里的残留值,请显式 return。
  • return 的类型会按赋值规则转换(float 返回值给 int 声明会截断)。

返回值经 X0 传递 ​

编译器把返回值放到 x0,调用方从 x0 取值;参数依次放入 x0..x(n-1)。这条约定与 ISA 层完全一致,因此 CIN 与汇编可以互相对照。

cin
function add(int a, int b) -> int {
    return a + b      // 结果写入 x0
}

function main() -> int {
    return add(2, 3)
}

返回类型与数组

函数返回类型不能写固长数组(-> int[3] 会报 Expected LBRACE but got LBRACKET)。 需要返回数组时写指针形式 -> int[]。

递归与栈深限制 ​

CIN 支持递归。每次调用消耗栈帧(局部变量槽 + 参数槽 + 返回地址),调用栈由 VM 栈承载。

cin
function fact(int n) -> int {
    if (n <= 1) { return 1 }
    return n * fact(n - 1)
}

function fib(int n) -> int {
    if (n < 2) { return n }
    return fib(n - 1) + fib(n - 2)
}

function main() -> int {
    println(int_to_str(fact(10)))   // 3628800
    println(int_to_str(fib(20)))    // 6765
    return 0
}
配置项默认值说明
内存总量 --mem-size1073741824(1 GiB,稀疏分页按需提交,可用 --mem-size 覆盖)栈与堆共享这块地址空间
栈顶(mem_size - 8) & ~0x7栈从内存顶端向下生长;函数序言用 SYS 137 ALLOCFRAME 分配帧(带栈余量防护)
指令上限 --max-instructions不限制(已弃用,隐藏参数)旧版为 100000000;默认不再因步数截断长循环

递归过深或局部大数组超出栈余量时,函数序言的帧分配会报:

text
Stack overflow: frame needs 80000 bytes, stack headroom only 12344 bytes
(SP 0x2000e0f8, guard 0x20001000, memory 1073741824 bytes).
Try --mem-size (default 1073741824) or smaller local arrays

PUSH 直接撞上堆则报 Stack overflow (collides with heap)。

默认 1 GiB 内存已能容纳很深的递归,一般无需调整;若仍不足可加大 --mem-size,但改不了递归算法本身的复杂度:

bash
codecin deep.cin --mem-size 1048576

递归是 O(depth) 栈消耗

fib(35) 这类指数递归不只是“栈深”问题,指令数也会爆炸。深度递归建议先改成迭代。

两遍编译:先用后定义 ​

parse_program 先顺序扫描所有 function 与 struct 定义收集签名,再生成代码,所以:

  • 同文件内的函数互相调用不受书写顺序限制;
  • 函数可以调用写在它后面的函数;
  • 但变量必须先声明后使用(见下节)。
cin
function main() -> int {
    return helper(3)        // helper 定义在后面,仍然合法
}

function helper(int x) -> int {
    return x * 2
}

变量必须先声明 ​

与函数不同,变量没有隐式声明。赋值一个从未声明过的名字会直接编译失败:

cin
function main() -> int {
    zzz = 1                 // Compiler error: Undefined variable: zzz
    return 0
}
text
┌──────────────────────── Load Error ────────────────────────┐
│ prog.cin:2: Compiler error: Undefined variable: zzz        │
└────────────────────────────────────────────────────────────┘

正确写法:

cin
function main() -> int {
    int zzz = 1
    return zzz
}

全局变量只能使用常量初始化表达式:int g = 1 + 2 合法,int g = abs(-3) 会报 Non-constant global initializer: call。需要调用函数请放进 main 或某个函数里。

完整示例:多函数程序 ​

下面这个程序把值传递、引用可见、固长数组衰减、递归、返回值串联起来,可直接运行:

cin
// stats.cin —— 运行: codecin stats.cin

struct Range {
    int lo
    int hi
}

// 值传递: 不影响调用方
function clamp_int(int v, int lo, int hi) -> int {
    if (v < lo) { return lo }
    if (v > hi) { return hi }
    return v
}

// 数组引用可见: 原地改写
function fill_zero(int a[8]) {
    for (int i = 0; i < 8; i = i + 1) {
        a[i] = 0
    }
}

function fill_range(int a[8], int lo, int hi) {
    for (int i = 0; i < 8; i = i + 1) {
        a[i] = lo + i
        if (a[i] > hi) { a[i] = hi }
    }
}

// 参数是 struct, 引用可见; 返回 int 经 X0
function span(Range r) -> int {
    return r.hi - r.lo
}

// 递归
function sum_to(int n) -> int {
    if (n <= 0) { return 0 }
    return n + sum_to(n - 1)
}

function main() -> int {
    int data[8]

    fill_zero(data)
    println("zero sum = " + int_to_str(data[0] + data[7]))   // 0

    fill_range(data, 30, 33)
    println("data[0]=" + int_to_str(data[0])
            + " data[3]=" + int_to_str(data[3])
            + " data[7]=" + int_to_str(data[7]))             // 30 33 33

    Range r
    r.lo = 10
    r.hi = 42
    println("span = " + int_to_str(span(r)))                 // 32

    println("clamp(99, 0, 10) = "
            + int_to_str(clamp_int(99, 0, 10)))              // 10
    println("sum_to(100) = " + int_to_str(sum_to(100)))      // 5050

    return span(r)
}

预期输出:

text
zero sum = 0
data[0]=30 data[3]=33 data[7]=33
span = 32
clamp(99, 0, 10) = 10
sum_to(100) = 5050

常见函数相关错误 ​

错误信息原因修正
Unknown function: xxx调用了未定义或拼错的函数检查拼写,或补上函数定义
Undefined variable: xxx变量未声明就使用先 int x = 0 声明
Expected IDENT but got LBRACKET ('[')参数把维度写在类型名上(int[3] a)改成 int a[3]
Expected LBRACE but got LBRACKET ('[')返回类型用了固长数组 -> int[3]改成 -> int[]
Expected RBRACE ... at line N花括号不配对 / 块内缺换行检查第 N 行附近括号
Stack overflow: frame needs ... / Stack overflow (collides with heap)递归过深 / 局部大数组超栈余量 / PUSH 撞堆减少深度、缩小局部数组,或 --mem-size 扩容
Non-constant global initializer: call全局变量用函数调用初始化改到函数体内赋值

相关页面 ​

文档由 VitePress 构建