Skip to content

unsafe 与指针 ​

unsafe ​

危险操作必须放在 bu_anquan(不安全 / unsafe)块里。编译器不保证这些操作的安全,由写代码的人负责:

cpro
bu_anquan {
    // 危险操作
}
cpro
不安全 {
    // 危险操作
}
cpro
unsafe {
    // 危险操作
}

需要 bu_anquan 的操作:

  • 调用 C 函数,见 调用 C 库
  • 对局部变量、参数取地址
  • 全部裸地址操作
  • C 字符串与缓冲区转换(c_chuan、c_zhuan_chuan、zhi_zhuan_shuzu 等)

推荐用普通函数把 bu_anquan 包起来,业务代码保持干净。审计时搜索 bu_anquan 就能找到所有危险的地方:

cpro
wailian hs pow(x f64, y f64) f64

hs mypow(x f64, y f64) f64 {
    bu_anquan { fanhui pow(x, y) }
}
cpro
外联 函数 pow(x f64, y f64) f64

函数 mypow(x f64, y f64) f64 {
    不安全 { 返回 pow(x, y) }
}
cpro
extern fn pow(x f64, y f64) f64

fn mypow(x f64, y f64) f64 {
    unsafe { return pow(x, y) }
}

带类型地址 ​

cpro 的指针叫带类型地址,类型写作 daizhi(T)(带址(T) / addr(T))。

拼音中文英文作用需要 unsafe
quzhi(x)取址(x)addr_of(x)取地址取局部变量、参数的地址时需要
jiezhi(p)解址(p)deref(p)读不需要
xiezhi(p, v)写址(p, v)store(p, v)写不需要
cpro
bl x leixing z64 = 16                   // 模块级变量

hs demo() kongbai {
    bl p leixing daizhi(z64) = quzhi(x)  // 模块级变量取地址,不需要 unsafe
    dayin(jiezhi(p))                     // 16
    xiezhi(p, 42)                        // x 变成 42
}
cpro
变量 x 类型 i64 = 16                    // 模块级变量

函数 demo() void {
    变量 p 类型 带址(i64) = 取址(x)     // 模块级变量取地址,不需要 unsafe
    打印(解址(p))                       // 16
    写址(p, 42)                         // x 变成 42
}
cpro
var x type i64 = 16                     // 模块级变量

fn demo() void {
    var p type addr(i64) = addr_of(x)   // 模块级变量取地址,不需要 unsafe
    print(deref(p))                     // 16
    store(p, 42)                        // x 变成 42
}

取局部变量或参数的地址必须在 bu_anquan 里,因为函数返回后这个地址就失效了:

cpro
hs foo() kongbai {
    bl local leixing z64 = 1
    bu_anquan {
        bl p leixing daizhi(z64) = quzhi(local)
        xiezhi(p, 2)
    }
    dayin(local)        // 2
}
cpro
函数 foo() void {
    变量 local 类型 i64 = 1
    不安全 {
        变量 p 类型 带址(i64) = 取址(local)
        写址(p, 2)
    }
    打印(local)         // 2
}
cpro
fn foo() void {
    var local type i64 = 1
    unsafe {
        var p type addr(i64) = addr_of(local)
        store(p, 2)
    }
    print(local)        // 2
}

规则:

  • 只能对有名字的东西取地址:变量、结构体字段、数组元素;不能对临时值或函数返回值取地址
  • 字段、元素的地址跟随所属变量:属于局部变量就需要 bu_anquan
  • 地址类型必须和变量类型一致,z64 变量的地址不能当 daizhi(f32) 用
  • 地址不能为空,也不能做加减运算
  • 没有多级地址,daizhi(daizhi(T)) 会报错
  • 两个地址可以用 == 比较是否指向同一处
  • 地址可以作参数和返回值,但返回局部变量的地址会悬垂,要自己保证不这样用

裸地址 ​

裸地址就是一个 z64(i64)整数,用于和 C 互操作。所有裸地址操作都必须在 bu_anquan 里:

拼音中文英文作用
luozhi(x)裸址(x)raw_addr(x)取裸地址
luojie(p, T)裸解(p, T)raw_deref(p, T)按类型读
luoxie(p, v, T)裸写(p, v, T)raw_store(p, v, T)按类型写
luozhuanzhi(p, T)裸转址(p, T)raw_to_addr(p, T)裸地址转带类型地址
zhizhuanluo(a)址转裸(a)addr_to_raw(a)带类型地址转裸地址
cpro
bl x leixing z64 = 16

hs demo() kongbai {
    bu_anquan {
        bl p leixing z64 = luozhi(x)
        dayin(luojie(p, z64))               // 16
        luoxie(p, 42, z64)

        bl a leixing daizhi(z64) = luozhuanzhi(p, z64)
        bl r leixing z64 = zhizhuanluo(a)
    }
}
cpro
变量 x 类型 i64 = 16

函数 demo() void {
    不安全 {
        变量 p 类型 i64 = 裸址(x)
        打印(裸解(p, i64))                  // 16
        裸写(p, 42, i64)

        变量 a 类型 带址(i64) = 裸转址(p, i64)
        变量 r 类型 i64 = 址转裸(a)
    }
}
cpro
var x type i64 = 16

fn demo() void {
    unsafe {
        var p type i64 = raw_addr(x)
        print(raw_deref(p, i64))            // 16
        raw_store(p, 42, i64)

        var a type addr(i64) = raw_to_addr(p, i64)
        var r type i64 = addr_to_raw(a)
    }
}

指针在 cpro 里主要用于调用 C 库,日常代码用 借用 就够了。

cpro 目前处于测试阶段