diff --git a/clang/docs/BSC/BiShengCLanguageUserManual.md b/clang/docs/BSC/BiShengCLanguageUserManual.md index d081aac2ab10568a40be41e747d95693ebd1eced..9712eb9982c3a472b7dbd4d8419cb1028b79114e 100644 --- a/clang/docs/BSC/BiShengCLanguageUserManual.md +++ b/clang/docs/BSC/BiShengCLanguageUserManual.md @@ -2281,7 +2281,464 @@ clang -rewrite-bsc boo.cbs foo.cbs 毕昇 C 内存管理的目标是将常见的时间类内存安全问题,如悬挂引用/内存泄漏/重复释放堆内存/解引用空指针等常见的内存安全问题在编译阶段暴露出来。 -为此,毕昇 C 引入了所有权和借用两个新的概念。 +为此,毕昇 C 引入了[所有权](###所有权)和[借用](###借用)两个新的概念,所有权用关键字`owned`实现,借用通过关键字`borrow`表示,并通过`safe`和`unsafe`两个关键字来约束所有权和借用的执行范围。 + +### 安全区 + +#### 概述: + +c 语言有很多规则过于灵活,不方便编译器做静态检查。因此我们引入一个新语法,使得在一定范围内的毕昇 c 代码必须遵循更严格的约束,保证在这个范围内的代码肯定不会出现“内存安全”问题。 + +允许用`safe`和`unsafe`关键字修饰函数、语句、括号表达式。 + +- `unsafe`表示这段代码在非安全区,这部分代码遵循标准 c 的规定,同时这部分代码的安全性由用户保证。 + + +- `safe`表示这段代码在安全区,这部分代码必须遵循更严格的约束,同时编译器可以保证内存安全。 + + +- 没有 `safe`或`unsafe`关键字修饰的全局函数默认是非安全的。 + + +#### 代码示例: + +```c +#include + +typedef struct File { +} FileID; + +// 无关键字修饰,表示按默认非安全区 +FileID *owned create(void) { + FileID *p = malloc(sizeof(FileID)); + return (FileID * owned) p; +} +FileID *owned consume_and_return(FileID *owned p) { return p; } + +// 使用 safe 修饰函数,表示该函数为安全函数,函数内为安全区 +safe void file_safe_free(FileID *owned p) { + // 使用 unsafe + // 修饰代码块,表示这段代码在非安全区。这段代码是在安全区内的非安全区,也属于非安全区 + unsafe { free((FileID *)p); } +} + +int main(void) { + FileID *owned p1 = create(); + FileID *owned p2 = consume_and_return(p1); + // 使用 safe 修饰语句,表示这段代码在安全区 + safe file_safe_free(p2); + return 0; +} +``` + +#### 语法规则: + +1. 允许使用`safe/unsafe`修饰函数声明、函数签名、函数定义、函数指针、语句、括号表达式。 + +```c +// 修饰函数签名 +safe int test(int, int); +// 修饰函数声明 +safe int test(int a, int b); +// 修饰函数定义 +safe int test(int a, int b) { return a + b; } +// 修饰函数指针 +safe int (*p)(int, int); + +safe int main(void) { + // 修饰代码块 + safe { + int a = 1; + } + unsafe { int b = 1; } + // 修饰语句 + safe int c = 1; + unsafe c++; + // 修饰括号表达式 + char d = unsafe((char)c); + return 0; +} +``` + +2. 不允许使用`safe/unsafe`修饰全局变量、函数外类型声明、`typedef`声明(允许修饰函数指针)。 + +```c +// error: 不允许修饰全局变量 +safe int g_a; +// error: 不允许修饰函数外类型声明 +safe struct b { int a; }; +// error: 不允许修饰 typedef +safe typedef int mm; + +int main() { return 0; } +``` + +3. `safe`修饰的函数,参数类型和返回类型必须是`safe`类型。 + + 非`safe`类型包括:裸指针类型、`union`类型、成员中包含不安全类型的`struct`类型、成员中包含不安全类型的数组类型。 + +```c +// error: 返回值为非安全类型的裸指针类型 +safe int *test1(int a); +// error: 参数类型为非安全类型的裸指针类型 +safe int test2(int *a); + +typedef struct F { + int *a; +} SF; +// error: 返回值为成员中包含不安全裸指针类型的 struct 类型 +safe SF test3(int a); +// error: 参数类型为成员中包含不安全裸指针类型的 struct 类型 +safe int test4(SF b); +``` + +4. `safe`修饰的函数,函数参数列表不可以省略。`safe void test(); `是不允许的, `safe void test(void); `是允许的。 + +5. `safe`修饰的函数,函数参数列表不可以包含变长参数。`safe int test(int a, ...); `是不允许的。 + +6. 如果`trait`中的函数被声明为`safe`,那么要求实现`trait`的类型的对应成员函数也必须是`safe`修饰的函数。若`trait`中的函数未声明为`safe`,也允许实现`trait`中的类型的成员函数为`safe`,但编译器会给出**warning**。 + +```c +trait G { + safe int *owned test1(This * owned this); + int *owned test2(This * owned this); +}; +// trait 实现函数必须为 safe +safe int *owned int ::test1(int *owned this) { return this; } +// 非 safe 函数的实现可以为 safe +safe int *owned int ::test2(int *owned this) { return this; } +impl trait G for int; + +int main() { return 0; } +``` + +7. 多个同名函数声明必须有同样的`safe/unsafe`修饰。 + +```c +safe int test(int a); +// error: 多个函数声明中 safe/unsafe 不一致 +unsafe int test(int a); +``` + +8. 多个同名函数声明排除`safe/unsafe`修饰后,应是完全一致的。 + +```c +safe int test(int a); +// error: 函数声明不完全一致 +safe int test(int a, int b); +``` + +9. `safe`修饰泛型函数时,会对泛型每个实例化版本也做`safe`检查。 + +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +safe T F(T a) { return a; } + +void test() { + int a = 1; + int b = F(a); + int *owned c = (int *owned)safe_malloc(1); + int *owned d = F(c); + // error: 实列化函数入参和返回值为非安全的裸指针类型 + int *e = F((void *)0); + safe_free((void *owned)d); +} + +int main() { + test(); + return 0; +} +``` + +10. 成员函数也可以被`safe/unsafe`修饰,其规则和全局函数一样。 + +```c +struct MyStruct { + T res; +}; +safe T struct MyStruct::foo_a(T a) { + return a; +} + +int main() { return 0; } +``` + +11. 对于`safe`修饰的函数指针类型,与赋值的函数参数和返回值类型必须是一致的。 + +```c +safe void test1(int a) {} +safe void test2(void) {} +safe void (*p)(int a); +int main() { + p = test1; + // error: 参数类型不一致,不允许赋值 + p = test2; + return 0; +} +``` + +12. 安全区内被调用的函数或函数指针必须是`safe`的函数签名,不允许调用非安全函数或函数指针。 + +```c +safe void test1(void) {} +unsafe void test2(void) {} +safe void (*test3)(void); +unsafe void (*test4)(void); +int main() { + safe { + test1(); + // error: 安全区内不允许调用非安全函数 + test2(); + test3(); + // error: 安全区内不允许调用非安全函数指针 + test4(); + } + unsafe { + test1(); + test2(); + test3(); + test4(); + } +} +``` + +13. 安全区内允许再包含`unsafe`修饰的语句、函数指针、括号表达式,非安全区内也允许再包含`safe`修饰的语句、函数指针、括号表达式。 + +```c +int test1(int a, int b) { return a + b; } +safe int test2(int a, int b) { return a > b ? a : b; } +int main() { + safe { + int a = 0; + unsafe a++; + unsafe { + a = test1(1, 3); + safe a = test2(3, 5); + } + } +} +``` + +14. 安全区内不允许无初始化或初始化不完整的变量声明。 + +```c +struct S { + int age; + char name[20]; +}; +void test() { + safe { + // error: 安全区内不允许无初始化的变量声明 + int a; + // error:安全区不允许部分初始化 + struct S tom = {10}; + struct S tony = {10, "tony"}; + } +} + +int main() { + test(); + return 0; +} +``` + +15. 安全区内`switch`语句中的`case/default`只能存在于`switch`后面的第一层代码块中,且第一层代码块不允许有变量定义。 + +```c +safe void test(int a) { + switch (a) { + // error: 第一层代码块不允许有变量定义 + int b = 10; + case 0: { + int c = 1; + break; + } + { + // error: case 只能存在于 switch 后面的第一层代码块中 + case 1 : { break; } + } + { + // error: default 只能存在于 switch 后面的第一层代码块中 + default: { break; } + } + } +} + +int main() { + int a = 1; + test(a); + return 0; +} +``` + +16. 安全区内不允许使用自增 (++)、自减(--)操作符,不允许`union`类型通过`.`访问成员,不允许裸指针通过`->`访问成员, + + 允许owned指针和borrow指针通过`->`访问成员。 + +```c +union un { + int age; + char name[16]; +}; +struct F { + int age; +}; + +void test(void) { + struct F d = {10}; + struct F *e = &d; + struct F *owned f = (struct F * owned) & d; + struct F *borrow i = &mut d; + safe { + int a = 1; + // error: 安全区不允许自增 + a++; + // error: 安全区不允许自减 + a--; + // 安全区允许 += 运算符 + a += 1; + // 安全区允许 -= 运算符 + a -= 1; + union un b = {10}; + // error: 安全区不允许 union 通过“.”访问成员 + int c = b.age; + // error: 安全区不允许裸指针通过“->”访问成员 + int g = e->age; + // 允许owned指针“->”通过访问成员 + int h = f->age; + //允许borrow指针“->”通过访问成员 + int j = i->age; + } +} + +int main() { + test(); + return 0; +} +``` + +17. 安全区内不允许使用取地址符`&`(允许对函数取地址),只允许`&const`,`&mut`取借用。 + + 安全区内不允许解引用裸指针类型,但可以解引用`owend`指针类型和`borrow`指针类型。 + +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +void test() { + safe { + int a = 10; + // error: 安全区不允许取地址符号 + int *b = &a; + // error: 安全区不允许解引用裸指针 + int c = *b; + int *owned d = safe_malloc(2); + // 允许解引用owned指针 + int e = *d; + safe_free((void *owned)d); + int *borrow f = &mut a; + // 允许解引用borrow指针 + int g = *f; + } +} + +int main() { + test(); + return 0; +} +``` + +18. 安全区内不允许指向类型不同的指针类型之间转换,不允许指针和非指针类型之间的转换,不允许`owned/borrow/raw`指针之间的转换。 + +```c +void test() { + int *pa; + double *pb; + safe { + // error:不允许指向类型不同的指针类型之间转换 + pb = pa; + // error:不允许指向类型不同的指针类型之间转换 + pa = pb; + // error:不允许指向类型不同的指针类型之间转换 + pb = (double *)pa; + } + int i; + safe { + // error:不允许指针和非指针类型之间的转换 + pa = i; + // error:不允许指针和非指针类型之间的转换 + i = pa; + } + int *owned pd = (int *owned) & pc; + safe { + // error:不允许 owned/raw 指针之间的转换 + pa = pd; + // error:不允许 owned/raw 指针之间的转换 + pd = pa; + } +} + +int main() { + test(); + return 0; +} +``` + +19. 安全区内不允许表达范围从大向小的类型转换(比如从`long`转换为`int`,从`int`转换为`_Bool`,从`int`转换为`enum`)。不允许表达精度从高向低的类型转换(比如从`double`转换为`float`)。对于基础类型的常量发生类型转换,如果目标类型可以描述这个值,那么该类型转换是允许的,并且在`if/while`中也允许转换。 + +```c +void test() { + long a; + int b; + _Bool c; + double e; + float f; + safe { + // 允许表达范围从小向大的类型转换 + a = b; + // error: 不允许表达范围从大向小的类型转换 + b = a; + a = 1; + // _Bool可以描述1 + c = 1; + // error: 目标类型不可以描述这个值,不允许转换 + c = 2; + e = f; + // error:不允许表达精度从高向低的类型转换 + f = e; + f = 1.0; + f = 1.2f; + } +} + +int main() { + int a = 2; + safe { + // 在 if 中允许转换 + if (a) { + a += 1; + } else { + a -= 1; + } + } + test(); + return 0; +} +``` + +20. 安全区内不允许内嵌汇编语句。 + +```c +void test() { + safe { + int ret = 0; + int src = 1; + // error: 安全区不允许内嵌汇编 + asm("move %0, %1\n\t" : "=r"(ret) : "r"(src)); + } +} +int main() { return 0; } +``` ### 所有权 @@ -2292,7 +2749,7 @@ C 语言作为一种系统级编程语言, 因而被广泛地应用于各种需要直接与硬件或内存等系统资源交互的领域和场景。 然而,这种内存管理模式存在容易导致内存泄漏、释放后使用、空指针解引用、缓冲区溢出和越界读写等内存安全问题。 内存安全问题不仅会造成资源的浪费,也可能导致程序行为错误,甚至导致程序崩溃,对程序的稳定性造成威胁。 -内存安全问题可以划分为时间内存安全和空间内存安全两大类,其中时间内存安全包含内存泄漏、释放后使用、空指针解引用等,空间内存安全包括缓冲区溢出、越界读写等。 +内存安全问题可以划分为**时间内存安全**和**空间内存安全**两大类,其中时间内存安全包含内存泄漏、释放后使用、空指针解引用等,空间内存安全包括缓冲区溢出、越界读写等。 BiShengC 语言的内存管理为解决程序的时间内存安全问题,利用了**所有权特性**在编译期对潜在的内存安全问题进行检查,识别潜在的时间内存安全错误。 @@ -2307,23 +2764,72 @@ BiShengC 语言的所有权特性被用于确保程序中的指针及其指向 ```c #include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 -int * owned takes_and_gives_back(int * owned p) { - return p; -} +int *owned takes_and_gives_back(int *owned p) { return p; } safe void test(void) { - int * owned p = safe_malloc(2); // 通过提供的 safe_malloc 申请一块大小为 sizeof(int) 的堆内存,并将值设置为2 - int * owned q = p; // 将 p 指向的堆内存转移给 q,后续不可再使用 p 访问这块内存,否则编译报错 - unsafe { - q = takes_and_gives_back(q); // 通过函数参数转移走 q 的所有权,但通过函数返回值归还所有权 - safe_free((void * owned)q); // 在 q 的作用域结束前调用 safe_free 安全释放堆内存,此处不释放则会报内存泄漏错误 - } - return; + // 通过提供的 safe_malloc 申请一块大小为 sizeof(int) 的堆内存,并将值设置为2 + int *owned p = safe_malloc(2); + // 将 p 指向的堆内存转移给 q,后续不可再使用 p 访问这块内存,否则编译报错 + int *owned q = p; + unsafe { + // 通过函数参数转移走 q 的所有权,但通过函数返回值归还所有权 + q = takes_and_gives_back(q); + // 在 q 的作用域结束前调用 safe_free + // 安全释放堆内存,此处不释放则会报内存泄漏错误 + safe_free((void *owned)q); + } + return; +} + +int main() { + test(); + return 0; } ``` -注:在安全区,`owned`指针指向的内存一定为通过`safe_malloc`函数申请出的堆内存,`owned`指针不可能指向栈内存。 +在安全区,`owned`指针指向的内存一定为通过`safe_malloc`函数申请出的堆内存,`owned`指针不可能指向栈内存,在指针生命周期结束前,必须通过`safe_free`函数进行释放。 +这两个函数的函数原型如下: +```c +T *owned safe_malloc(T t); +void safe_free(void *owned); +``` +可以通过`safe_malloc`申请足够存放`T`类型的堆空间,并初始化为`t`,当`owned`指针生命周期结束前,必须通过`safe_free`进行释放,在使用时,需要将`T *owned`指针转换为`void *owned`指针类型再释放,对于**多级指针,需要从内到外进行释放**;对于结构体内部有`owned`指针成员的情况,需要**先将结构体内全部`owned`指针成员释放后,才能释放结构体的`owned`指针**。 + +函数使用示例: +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +struct S { + int *owned p; + int *owned q; +}; + +safe void test(void) { + //变量所有权初始化 + int *owned pi = safe_malloc(1); + // 结构体所有权初始化 + struct S s = {.p = safe_malloc(2), .q = safe_malloc(3)}; + struct S *owned s1 = safe_malloc(s); + // 多级指针所有权初始化 + int *owned p = safe_malloc(1); + int *owned *owned pp = safe_malloc(p); + // 变量所有权释放 + safe_free((void *owned)pi); + // 结构体所有权释放(从内到外) + safe_free((void *owned)s1->p); + safe_free((void *owned)s1->q); + safe_free((void *owned)s1); + // 多级指针所有权释放(从内到外) + safe_free((void *owned) * pp); + safe_free((void *owned)pp); +} + +int main() { + test(); + return 0; +} +``` #### 2. 语法及语义规则 @@ -2344,189 +2850,279 @@ ownership-qualifier: 2. `owned`关键字仅被允许用于修饰指针类型,不允许修饰非指针类型,修饰多级指针时,每级指针的类型修饰可以不一样,规则与`const`类似; - ```c - safe void test(void) { - int * owned p = safe_malloc(2); // 允许 - // int owned a = 2; // 不允许 - double * owned q = safe_malloc(1.1); // 允许 - // double owned b = 1.1; // 不允许 - int * owned * owned pp = safe_malloc(p); // 允许 - unsafe { - double * d = (double *)malloc(sizeof(double)); - double * * owned qq = safe_malloc(d); // 允许 - safe_free((void* owned* owned)pp); - safe_free((void* owned)p); - safe_free((void** owned)qq); - } - } - ``` +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +safe int main(void) { + int *owned p = safe_malloc(2); + // owned 关键字必须修饰指针 + int owned a = 2; + double *owned q = safe_malloc(1.1); + // owned 关键字必须修饰指针 + double owned b = 1.1; + // 可以修饰多级指针 + int *owned *owned pp = safe_malloc(p); + unsafe { + double *d = (double *)malloc(sizeof(double)); + double **owned pd = safe_malloc(d); + // 多级指针释放时,应当释放从内向外释放 + safe_free((void *owned) * pp); + safe_free((void *owned)pp); + free(*pd); // 也可直接 free(d); + safe_free((void *owned)pd); + } + safe_free((void *owned)q); + return 0; +} +``` 3. 允许使用`owned`关键字修饰结构体指针及结构体的指针成员; - ```c - struct S { - int m; - int n; - }; +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 - struct R { - int * owned p; // 允许 - double * owned q; // 允许 - } +struct S { + int m; + int n; +}; - safe void test(void) { - struct S s = { .m = 1, .n = 2 }; - struct S * owned sp = safe_malloc(s); // 允许 - // struct S owned so = { .m = 1, .n = 2 }; // 不允许 - struct R r = { .p = safe_malloc(1), .q = safe_malloc(2.5) }; // 允许 - struct R * owned rp = safe_malloc(r); // 允许 - // struct R owned ro = { .p = safe_malloc(2), .q = safe_malloc(1.5) }; // 不允许 - } - ``` +struct R { + int *owned p; + double *owned q; +}; + +safe void test(void) { + struct S s = {.m = 1, .n = 2}; + struct S *owned sp = safe_malloc(s); + // error: 要用 safe_malloc 初始化 owned 指针成员 + struct S owned so = {.m = 1, .n = 2}; + struct R r = {.p = safe_malloc(1), .q = safe_malloc(2.5)}; + struct R *owned rp = safe_malloc(r); + safe_free((void *owned)sp); + // 先释放结构体内的 owned 指针成员 + safe_free((void *owned)rp->p); + safe_free((void *owned)rp->q); + // 再释放结构体 owned 指针 + safe_free((void *owned)rp); +} + +int main() { + test(); + return 0; +} +``` 4. `owned`不允许修饰`union`类型和`union`的成员,且`union`的每个成员均不能拥有`owned`修饰的成员; - ```c - struct S { - int a; - int b; - }; +```c +struct S { + int a; + int b; +}; - struct T { - int * owned p; - struct S S; - }; +struct T { + int *owned p; + struct S s; +}; - union A { - int a; - // int * owned p; // 不允许 - struct S s; - // struct S * owned sp; // 不允许 - // struct T t; // 不允许 - // struct T * owned tp; // 不允许 - struct T * trp; // 允许,不跟踪裸指针指向的变量的 owned 成员 - }; - ``` +union A { + int a; + // 禁止 union 成员用 owned 修饰 + int *owned p; + struct S s; + // 禁止 union 成员用 owned 修饰 + struct S *owned sp; + // 禁止 union 成员结构体有 owned 成员 + struct T t; + // 禁止 union 成员用 owned 修饰 + struct T *owned tp; + // 不跟踪裸指针指向的变量的 owned 成员 + struct T *trp; +}; +``` 5. `owned`修饰的类型或拥有`owned`修饰的成员的类型不可以作为数组的成员; - ```c - struct A { - int * owned p; - }; +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 - safe void test(void) { - // int * owned arr_i[2] = ...; // 不允许 - // struct A arr_a[2] = ...; // 不允许,struct A 拥有 owned 修饰的成员 - } - ``` +struct A { + int *owned p; +}; + +safe void test(void) { + // 数组成员不能被 owned 修饰 + int *owned arr_i[2] = {safe_malloc(1), safe_malloc(2)}; + // 数组成员不能被 owned 修饰 + struct A arr_a[2] = {{safe_malloc(1)}, {safe_malloc(2)}}; +} +``` 6. `owned`修饰的指针不支持算术运算符(指针偏移操作),但支持比较运算符; - ```c - safe void test(void) { - int * owned p = safe_malloc(2); - int * owned q = safe_malloc(3); - // p++; // 不允许 - // p[3] = ...; // 不允许 - if (p == q) { // 允许 - // ... - } - unsafe { - safe_free((void* owned)p); - safe_free((void* owned)q); - } - } - ``` +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +safe int main(void) { + int *owned p = safe_malloc(2); + int *owned q = safe_malloc(3); + // owned 指针不支持算术运算符 + p += 1; + // owned 指针不支持 [] 运算符 + p[3] = 3; + // owned 指针支持比较运算符 + if (p == q) { + } + unsafe { + safe_free((void *owned)p); + safe_free((void *owned)q); + } + return 0; +} +``` 7. `owned`修饰的类型与非`owned`修饰的类型之间不允许隐式类型转换; - ```c - safe void test(void) { - unsafe { - int *b = (int*)malloc(sizeof(int)); - // int * owned p = b; // 不允许 - } - int * owned q = safe_malloc(3); - // int *c = q; // 不允许 - } - ``` +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +safe int main(void) { + unsafe { + int *b = (int *)malloc(sizeof(int)); + // owned 指针不允许隐式类型转换 + int *owned p = b; + } + int *owned q = safe_malloc(3); + // owned 指针不允许隐式类型转换 + int *c = q; + safe_free((void *owned)q); + return 0; +} +``` 8. 基本类型一致时,允许`owned`指针类型与非`owned`指针类型之间的显式强制类型转换,且这种转换只能在非安全区进行; 基本类型不一致时,只允许`void * owned`类型与`T * owned`类型之间的相互强制转换; - ```c - void test() { - int *b = (int *)malloc(sizeof(int)); - int * owned p = (int * owned) b; // 允许,b 和 p 的基本类型均为 int - double * d = (double *)malloc(sizeof(double)); - // int * owned q = (int * owned)d; // 不允许,基本类型不一致 - double * owned dd = safe_malloc(1.5); - void * owned v = (void * owned)dd; // 允许 - safe_free((void* owned)p); - safe_free((void* owned)d); - safe_free((void* owned)v); - } - ``` +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +int main() { + int *i = (int *)malloc(sizeof(int)); + // i 和 pi 的基本类型均为 int + int *owned pi = (int *owned)i; + double *d = (double *)malloc(sizeof(double)); + // d 和 pd 基本类型不一致 + int *owned pd = (int *owned)d; + double *owned dd = safe_malloc(1.5); + // 允许显式转换为 void *owned + void *owned pdd = (void *owned)dd; + safe_free((void *owned)pi); + safe_free((void *owned)d); + safe_free((void *owned)pdd); + return 0; +} +``` 9. `owned`允许修饰指向`trait`的指针,假设有一个具体类型`S`,它实现了`trait T`,则: - `S * owned`类型可以隐式转换为`trait T * owned`类型; - `trait T * owned`类型允许被显式转换为`void * owned`类型。 +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +trait T{}; + +impl trait T for int; + +void test() { + int *owned pi = safe_malloc(1); + // 隐式转换为 trait T *owned + trait T *owned pti = pi; + // 显式转换为 void *owned + void *owned pvi = (void *owned)pti; + safe_free(pvi); +} + +int main() { + test(); + return 0; +} +``` + 10. 通过函数指针调用函数的时候,规则与一般的函数调用一样,会在函数调用时检查形参类型与实参类型是否匹配及返回类型与返回值类型是否匹配; - ```c - int add(int *a, int *b); - typedef int (*FTP)(int *, int *); - typedef int (*FTOP)(int * owned, int *); - - void test() { - FTP ftp = add; - int * owned ap = safe_malloc(1); - int * owned bp = safe_malloc(2); - // ftp(ap, bp); // 不允许,类型不匹配 - // FTOP ftop = add; // 不允许,类型不匹配 - safe_free((void* owned)ap); - safe_free((void* owned)bp); - } - ``` +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +int add(int *a, int *b) { retun *a + *b; } +typedef int (*FTP)(int *, int *); +typedef int (*FTOP)(int *owned, int *); + +void test() { + FTP ftp = add; + int *owned pa = safe_malloc(1); + int *owned pb = safe_malloc(2); + // 类型不匹配 + ftp(pa, pb); + // 类型不匹配 + FTOP ftop = add; + safe_free((void *owned)pa); + safe_free((void *owned)pb); +} + +int main() { + test(); + return 0; +} +``` 11. `owned`可以修饰 trait 类型,即`trait T* owned`,也表示该变量拥有其内部存储的数据的所有权。 该类型可以作为类型声明、函数的入参类型及函数的返回值类型。但当前不支持`trait T* owned`与`trait T*`之间的类型转换。 - ```c - trait T { - safe void release(This* owned this); - }; - - struct IPv4 { - char* buf1; - }; - - struct IPv6 { - char* buf1; - char* buf2; - }; - - safe void struct IPv4::release(struct IPv4* owned this) { - free(this->buf1); - safe_free((void* owned)this); - } - - safe void struct IPv6::release(struct IPv6* owned this) { - free(this->buf1); - free(this->buf2); - safe_free((void* owned)this); - } - - impl trait T for IPv4; - impl trait T for IPv6; - - void cleanup(trait T* owned t) { - t->release(); - } - ``` +```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +trait T { safe void release(This * owned this); }; +struct IPv4 { + char *buf1; +}; +struct IPv6 { + char *buf1; + char *buf2; +}; +safe void struct IPv4::release(struct IPv4 *owned this) { + unsafe { free(this->buf1); } + safe_free((void *owned)this); +} +safe void struct IPv6::release(struct IPv6 *owned this) { + unsafe { + free(this->buf1); + free(this->buf2); + } + safe_free((void *owned)this); +} +impl trait T for struct IPv4; +impl trait T for struct IPv6; + +void cleanup(trait T *owned t) { t->release(); } + +int main() { + struct IPv4 ipv4 = {.buf1 = "192.168.1.1"}; + struct IPv6 ipv6 = {.buf1 = "2001:0db8:85a3:0000", + .buf2 = "0000:8a2e:0370:7334"}; + struct IPv4 *owned sipv4 = safe_malloc(ipv4); + struct IPv6 *owned sipv6 = safe_malloc(ipv6); + trait T *owned tipv4 = sipv4; + trait T *owned tipv6 = sipv6; + // 使用 trait T* owned 作为入参 + tipv4->release(); + tipv6->release(); + safe_free((void *owned)tipv4); + safe_free((void *owned)tipv6); + return 0; +} +``` #### 3. 所有权状态转移规则 @@ -2545,7 +3141,7 @@ ownership-qualifier: 例如,当一次函数调用完成后,其局部变量 p 被销毁,而其指向的堆内存未被显式地调用`free(p)`进行回收,则这块堆内存将永远无法被回收,产生了内存安全错误。
- +
利用 BiShengC 语言提供的所有权特性,可以用`owned`关键字对那些需要管理的指针进行标识,这样就可以在程序编译时检查出潜在的错误,避免在运行时出现错误。 @@ -2563,14 +3159,22 @@ ownership-qualifier: 以下是一段代码示例及说明: ```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + safe void test(void) { - int * owned p = safe_malloc(10); - int * owned q = p; - int * owned m = p; // error - unsafe { - safe_free((void* owned)q); - safe_free((void* owned)m); - } + int *owned p = safe_malloc(10); + int *owned q = p; + // error: p 的所有权已移交给q了 + int *owned m = p; + unsafe { + safe_free((void *owned)q); + safe_free((void *owned)m); + } +} + +int main() { + test(); + return 0; } ``` @@ -2584,22 +3188,27 @@ safe void test(void) { 以下是一段代码示例及说明: ```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + struct S { - int *p; - int * owned q; + int *p; + int *owned q; }; void test(void) { - struct S s = { - .p = (int *)malloc(sizeof(int)), - .q = safe_malloc(1) - }; - struct S * owned s1 = safe_malloc(s); - struct S * owned s2 = s1; - int * owned p = s1->q; // error - safe_free((void* owned)s2->q); - safe_free((void* owned)s2); - safe_free((void* owned)p); + struct S s = {.p = (int *)malloc(sizeof(int)), .q = safe_malloc(1)}; + struct S *owned s1 = safe_malloc(s); + struct S *owned s2 = s1; + // error: q 的所有权被一并交给 s2 了 + int *owned p = s1->q; + safe_free((void *owned)s2->q); + safe_free((void *owned)s2); + safe_free((void *owned)p); +} + +int main() { + test(); + return 0; } ``` @@ -2609,19 +3218,28 @@ void test(void) { 以下是一段代码示例及说明: ```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + struct S { - int * owned p; - int * owned q; + int *owned p; + int *owned q; }; safe void test(void) { - struct S s = { - .p = safe_malloc(2), - .q = safe_malloc(3) - }; - struct S * owned s1 = safe_malloc(s); - int * owned p = s1->p; - struct S * owned s2 = s1; // error + struct S s = {.p = safe_malloc(2), .q = safe_malloc(3)}; + struct S *owned s1 = safe_malloc(s); + int *owned p = s1->p; + // error: s1 已无对全部成员的所有权 + struct S *owned s2 = s1; + safe_free((void *owned)p); + safe_free((void *owned)s2->p); + safe_free((void *owned)s2->q); + safe_free((void *owned)s2); +} + +int main() { + test(); + return 0; } ``` @@ -2631,11 +3249,23 @@ safe void test(void) { 以下是一段代码示例及说明: ```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + safe void test(void) { - int * owned p = safe_malloc(10); - int * owned q = p; - p = safe_malloc(4); - int * owned m = p; + int *owned p = safe_malloc(10); + // 移走 p + int *owned q = p; + // 重新拿到新元素的所有权 + p = safe_malloc(4); + // 仍然可以将所有权交给其他指针 + int *owned m = p; + safe_free((void *owned)q); + safe_free((void *owned)m); +} + +int main() { + test(); + return 0; } ``` @@ -2645,10 +3275,20 @@ safe void test(void) { 以下是一段代码示例及说明: ```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + safe void test(void) { - int * owned p = safe_malloc(12); - int * owned q = safe_malloc(67); - q = p; // error + int *owned p = safe_malloc(12); + int *owned q = safe_malloc(67); + // error: p 有拥有其他指针的所有权 + q = p; + safe_free((void *owned)p); + safe_free((void *owned)q); +} + +int main() { + test(); + return 0; } ``` @@ -2658,19 +3298,27 @@ safe void test(void) { 以下是一段代码示例及说明: ```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + struct S { - int * owned p; - int * owned q; + int *owned p; + int *owned q; }; safe void test(void) { - struct S s = { - .p = safe_malloc(2), - .q = safe_malloc(3) - }; - struct S * owned s1 = safe_malloc(s); - struct S * owned s2 = s1; - s1->p = safe_malloc(5); // error + struct S s = {.p = safe_malloc(2), .q = safe_malloc(3)}; + struct S *owned s1 = safe_malloc(s); + struct S *owned s2 = s1; + // error: s1 已不再拥有所有权,无法给内部成员赋予所有权 + s1->p = safe_malloc(5); + safe_free((void *owned)s2->p); + safe_free((void *owned)s2->q); + safe_free((void *owned)s2); +} + +int main() { + test(); + return 0; } ``` @@ -2682,20 +3330,29 @@ safe void test(void) { 以下是一段代码示例及说明: ```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + struct S { - int * owned p; - int * owned q; + int *owned p; + int *owned q; }; safe void test(void) { - int * owned p = safe_malloc(2); - struct S s = { - .p = safe_malloc(2), - .q = safe_malloc(3) - }; - struct S * owned s1 = safe_malloc(s); - struct S * owned s2 = s1; -} // error + int *owned p = safe_malloc(2); + struct S s = {.p = safe_malloc(2), .q = safe_malloc(3)}; + struct S *owned s1 = safe_malloc(s); + struct S *owned s2 = s1; + // error: 未释放 s2 内部成员所有权及 s2 所有权;未释放 p 所有权 + // safe_free((void *owned)p); + // safe_free((void *owned)s2->p); + // safe_free((void *owned)s2->q); + // safe_free((void *owned)s2); +} + +int main() { + test(); + return 0; +} ``` 在这个例子中,当作用域结束时,编译器会发现`p`、`s2`、`s2->p`以及`s2->q`依然拥有其指向的堆内存的所有权,即这些堆内存都没有被释放,因此会编译失败并报告内存泄漏。 @@ -2706,51 +3363,31 @@ safe void test(void) { 以下是一段代码示例及说明: ```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + struct S { - int * owned p; - int * owned q; + int *owned p; + int *owned q; }; safe void test(void) { - struct S s = { - .p = safe_malloc(2), - .q = safe_malloc(3) - }; - struct S * owned s1 = safe_malloc(s); - int * owned p = s1->p; - unsafe { - safe_free((void * owned)s1); // error - } + struct S s = {.p = safe_malloc(2), .q = safe_malloc(3)}; + struct S *owned s1 = safe_malloc(s); + int *owned p = s1->p; + unsafe { + // error: s1 仍然拥有内部成员(q)所有权,无法转为 void *owned + safe_free((void *owned)s1); + safe_free((void *owned)p); + } } -``` - -在这个例子中,试图将`s1`强制类型转换为`void * owned`类型,但`s1->q`依然拥有所有权,因此转换失败。 - -**2. BiShengC 语言提供了`safe_free`函数用于释放`owned`指针变量指向的堆内存,该函数的入参要求为`void * owned`类型。** -以下是一段代码示例及说明: - -```c -struct S { - int * owned p; - int * owned q; -}; -safe void test(void) { - struct S s = { - .p = safe_malloc(2), - .q = safe_malloc(3) - }; - struct S * owned s1 = safe_malloc(s); - int * owned p = s1->p; - unsafe { - safe_free((void * owned)p); - safe_free((void * owned)s1->q); - safe_free((void * owned)s1); - } +int main() { + test(); + return 0; } ``` -在这个例子中,分别释放了`p`、`s1->q`以及`s1`指向的堆内存,不存在任何内存泄漏问题,使用`safe_free`完成了正确的堆内存释放。 +在这个例子中,试图将`s1`强制类型转换为`void * owned`类型,但`s1->q`依然拥有所有权,因此转换失败。 ##### 3.4 函数调用与返回 @@ -2758,27 +3395,36 @@ safe void test(void) { 以下是一段代码示例及说明: ```c +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + struct S { - int * owned p; - int * owned q; + int *owned p; + int *owned q; }; -struct S * owned foo(struct S s) { - struct S * owned ret = safe_malloc(s); - return ret; +struct S *owned F(struct S s) { + struct S *owned ret = safe_malloc(s); + return ret; } -safe void test(void) { - struct S s = { - .p = safe_malloc(2), - .q = safe_malloc(3) - }; - int * owned p = s.p; - struct S * owned s1 = foo(s); // error +void test(void) { + struct S s = {.p = safe_malloc(2), .q = safe_malloc(3)}; + int *owned p = s.p; + // error: s 已不再拥有全部内部成员的所有权 + struct S *owned s1 = F(s); + safe_free((void *owned)p); + safe_free((void *owned)s1->p); + safe_free((void *owned)s1->q); + safe_free((void *owned)s1); +} + +int main() { + test(); + return 0; } ``` -在这个例子中,传入`foo`函数的结构体变量`s`内部有两个`owned`指针变量,而`s.p`已经被转移走,因此这次函数调用是非法的,会编译报错。 +在这个例子中,传入`F`函数的结构体变量`s`内部有两个`owned`指针变量,而`s.p`已经被转移走,因此这次函数调用是非法的,会编译报错。 #### 4. 源源变换 @@ -2796,155 +3442,217 @@ BiShengC 语言的 clang 编译器支持源源变换功能,即将`.cbs`文件 #### 1.1 借用的定义及借用操作符 毕昇 C 的**借用是一个指针类型,指向了被借用对象存储的内存地址**。为表达借用的概念: 1. 引入新关键字`borrow`,用`borrow`来修饰指针类型 T*,表示 T 的借用类型 -2. 引入借用操作符 `&mut` 和 `&const`,其中,`&mut e`表示获取对表达式 e 的可变借用,`&const e`表示获取对表达式 e 的只读借用。此处要求表达式 e 是 lvalue,与标准 C 中的取地址操作符`&`类似, 借用操作符实际上获取的是表达式 e 的地址 +2. 引入借用操作符 `&mut` 和 `&const`,其中,`&mut e`表示获取对表达式 e 的**可变借用**,`&const e`表示获取对表达式`e`的**只读借用**。此处要求表达式`e`是 lvalue(左值)与标准 C 中的取地址操作符`&`类似, 借用操作符实际上获取的是表达式`e`的地址 -例如,我们可以创建指向局部变量 local 的可变借用 p1 和不可变借用 p2,并使用它们: +例如,我们可以创建指向局部变量`local`的可变借用`p1`和不可变借用`p2`,并使用它们: ```C -void use_immut(const int *borrow p); -void use_mut(int *borrow p); +void use_immut(const int *borrow p) {} +void use_mut(int *borrow p) {} void test() { - int local = 5; - int *borrow p1 = &mut local; //p1是可变借用指针,借用了local - use_mut(p1); - const int *borrow p2 = &const local; //p2是不可变借用指针,借用了local - use_immut(p2); + int local = 5; + // p1是local的可变借用指针 + int *borrow p1 = &mut local; + use_mut(p1); + // p2是local的不可变借用指针 + const int *borrow p2 = &const local; + use_immut(p2); +} + +int main() { + test(); + return 0; } ``` -另外,表达式 e 如果是指针的解引用表达式,`&mut *p`和`&const *p`分别可以看作对地址 p 中存放的值,也就是`*p`,取可变借用和不可变借用,这一操作不为`*p`产生临时变量。其中,p可以是裸指针、owned指针和其它借用指针。例如: +另外,表达式`e`如果是指针的解引用表达式,`&mut *p`和`&const *p`分别可以看作对地址`p`中存放的值,也就是`*p`,取可变借用和不可变借用,**这一操作不为`*p`产生临时变量**。其中,`p`可以是裸指针、`owned`指针和其它借用指针。例如: ```C -void test() { - int *x1 = malloc(sizeof(int)); - *x1 = 42; - int *borrow p1 = &mut *x1; //p1借用了*x1 +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 - int* owned x2 = safe_malloc(); - int *borrow p2 = &mut *x2; //p2借用了*x2 +void test() { + int *x1 = malloc(sizeof(int)); + *x1 = 1; + // p1借用了*x1 + int *borrow p1 = &mut * x1; + // p2借用了*x2 + int *owned x2 = safe_malloc(2); + int *borrow p2 = &mut * x2; + // p3借用了*x3 + int local = 3; + int *borrow x3 = &mut local; + int *borrow p3 = &mut * x3; + safe_free((void *owned)x2); +} - int local = 5; - int *borrow x3 = &mut local; - int *borrow p3 = &mut *x3; //p3借用了*x3 +int main() { + test(); + return 0; } ``` #### 1.2 借用的作用 假设我们有这样的一个需求:创建一个文件,并且调用一些操作函数对文件进行读写操作。如果没有借用的概念,调用文件操作函数会导致文件指针所有权的转移,为了使文件指针在函数调用之后仍然可以被使用,我们需要再将所有权返回给调用方: ```C -MyFile* owned create_file(); -void safe_free(MyFile* owned p); +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 +#include + +typedef struct { + int file_id; +} MyFile; -MyFile* owned insert_str(MyFile* owned p, char *str) { //字符串插入函数 - // some operation to insert a string to file - return p; //通过返回值,将所有权转移给调用方 +MyFile *owned create_file(int id) { + MyFile f = {.file_id = id}; + return safe_malloc(f); } +void file_safe_free(MyFile *owned p) { safe_free((void *owned)p); } -MyFile* owned other_operation(MyFile* owned p) { //对文件的其他操作函数 - // some operation - return p; //通过返回值,将所有权转移给调用方 +MyFile *owned insert_str(MyFile *owned p, char *str) { + // some operation to insert a string to file + printf("%s to file %d\n", str, p->file_id); + // 通过返回值,将所有权转移给调用方,避免所有权转移 + return p; } -int main(void) { - MyFile* owned p = create_file(); - p = insert_str(p, str); // p的所有权先被移动到insert_str中,再通过返回值转移到调用方 - p = other_operation(p); - safe_free(p); - return 0; +MyFile *owned other_operation(MyFile *owned p) { + // some operation + // 通过返回值,将所有权转移给调用方,避免所有权转移 + return p; +} + +int main() { + MyFile *owned p = create_file(0); + char str[] = "insert str"; + // p的所有权先被移动到 insert_str 中,再通过返回值转移到调用方 + p = insert_str(p, str); + // 同 insert_str + p = other_operation(p); + file_safe_free(p); + return 0; } ``` 这种写法会造成文件指针所有权的频繁转移,在代码逻辑较为复杂的时候很容易出错,而且如果所有权被转移走了但是没有归还,后续将无法再使用该文件指针。有了借用,将对文件指针的借用作为参数传递给操作函数,函数返回之后文件指针仍可以用于后续其他操作,不再需要像上面那个例子一样,先通过函数参数传入所有权,然后再通过函数返回来传出所有权,代码更加简洁: ```C -MyFile* owned create_file(); -void safe_free(MyFile* owned p); +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 +#include + +typedef struct { + int file_id; +} MyFile; -void insert_str(MyFile* borrow p, char *str) { //字符串插入函数,对文件指针的借用作为入参 - // some operation to insert a string to file +MyFile *owned create_file(int id) { + MyFile f = {.file_id = id}; + return safe_malloc(f); } +void file_safe_free(MyFile *owned p) { safe_free((void *owned)p); } -void other_operation(MyFile* borrow p) { //其他文件操作函数,对文件指针的借用作为入参 - // some operation +void insert_str(MyFile *borrow p, char *str) { + // some operation to insert a string to file + printf("%s to file %d\n", str, p->file_id); + // 无需返回所有权 } -int main(void) { - MyFile* owned p = create_file(); - insert_str(&mut *p, str); //不取得文件指针p的所有权,而是借用*p,后续p可以继续被使用 - other_operation(&mut *p); - safe_free(p); - return 0; +void other_operation(MyFile *borrow p) { + // some operation + // 无需返回所有权 +} + +int main() { + MyFile *owned p = create_file(0); + char str[] = "insert str"; + // 所有权不会被移动 + insert_str(&mut * p, str); + // 所有权不会被移动 + other_operation(&mut * p); + file_safe_free(p); + return 0; } ``` #### 2.借用变量和被借用对象的生命周期 ##### 2.1 生命周期及其作用 -我们可以对不同种类的对象取借用:owned变量、非owned类型的局部变量、全局变量、临时匿名变量、参数等,甚至是某个复合变量的一部分。为正确表示借用变量和不同种类被借用对象的有效作用域,我们引入生命周期的概念。 +我们可以对不同种类的对象取借用:`owned`变量、非`owned`类型的局部变量、全局变量、临时匿名变量、参数等,甚至是某个复合变量的一部分。为正确表示借用变量和不同种类被借用对象的有效作用域,我们引入生命周期的概念。 生命周期检查的主要作用是避免悬垂指针,它会导致程序使用本不该使用的数据,以下 C 代码就是一个使用了悬垂指针的典型例子: ```C -void test() { - int *p; - { - int local = 5; - p = &local; - } - use(p); +int main() { + int *p; + { + int local = 5; + p = &local; + } + *p = 1; + return 0; } - ``` 这段 C 代码有两点值得注意: -1. int *p 的声明方式存在使用 NULL 的风险; -2. `p`指向了内部 block 中的`local` 变量,但是`local`会在 block 结束的时候被释放,因此回到外部 block 后,`p`会指向一个无效的地址,是一个悬垂指针,它指向了提前被释放的变量`local`,可以预料到,`use(p)` 会导致该段程序运行时出现未定义行为(undefined behavior)。当代码逻辑较为复杂时,这类异常行为很难被发现。 +1. `int *p`的声明方式存在使用`NULL`的风险; +2. `p`指向了内部block中的`local` 变量,但是`local`会在block结束的时候被释放,因此回到外部block后,`p`会指向一个无效的地址,是一个悬垂指针,它指向了提前被释放的变量`local`,可以预料到,`*p = 1` 会导致该段程序运行时出现未定义行为(undefined behavior)。当代码逻辑较为复杂时,这类异常行为很难被发现。 -对于第一点,毕昇 C 规定:和裸指针不同,表示借用的 borrow 指针必须被初始化,表示对某一个具体对象的借用; +对于第一点,毕昇 C 规定:和裸指针不同,表示借用的`borrow`指针必须被初始化,表示对某一个具体对象的借用; 对于第二点,毕昇 C 规定:**任何一个对资源的借用,都不能比资源的所有者的生命周期长**。也就是说:借用变量的生命周期,不能比被借用对象的生命周期长。 接下来我们使用毕昇 C 的借用特性改写上面的 C 代码,通过检查借用变量和被借用对象的生命周期,在编译期就可以识别出潜在的内存安全风险: ```C -void test() { - int local = 5; - int *borrow p = &mut local; //借用指针变量 p 必须被初始化,否则会报错 - { - int local1 = 5; - p = &mut local1; //对 p 进行再赋值之后,p 不再借用 local,而是借用 local1 - } - use(p); // error,local1 的生命周期不够长 +int main() { + int local1 = 1; + //借用指针变量 p 必须被初始化,否则会报错 + int *borrow p = &mut local1; + { + int local2 = 2; + //对 p 进行再赋值之后,p 不再借用 local1,而是借用 local12 + p = &mut local2; + } + // error,local2 的生命周期不够长 + *p = 3; + return 0; } ``` ##### 2.2 借用变量和被借用对象 每个借用变量(也就是 borrow 指针变量)都会有一个或多个被借用对象,例如: ```C +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + int g = 5; void test(int a, int *owned b, int *c, struct S d) { - // 被借用对象是普通局部变量 - int local = 5; - int *borrow p1 = &mut local; //p1的被借用对象是local - int *borrow p2 = &mut *p1; //p2的被借用对象是*p1 - int *borrow p3 = p1; //p3的被借用对象是*p1 + // 被借用对象是普通局部变量 + int local = 5; + int *borrow p1 = &mut local; // p1的被借用对象是local + int *borrow p2 = &mut * p1; // p2的被借用对象是*p1 + int *borrow p3 = p1; // p3的被借用对象是*p1 + + // 被借用对象是owned变量 + int *owned x1 = safe_malloc(2); + int *borrow p4 = &mut * x1; // p4的被借用对象是*x1 - // 被借用对象是owned变量 - int *owned x1 = safe_malloc(2); - int *borrow p4 = &mut *x1; //p4的被借用对象是*x1 + // 被借用对象是裸指针变量 + int *x2 = malloc(sizeof(int)); + int *borrow p5 = &mut * x2; // p5的被借用对象是*x2 - // 被借用对象是裸指针变量 - int *x2 = malloc(sizeof(int)); - int *borrow p5 = &mut *x2; //p5的被借用对象是*x2 + // 被借用对象是结构体的某个字段 + struct S s = {.a = 5}; + int *borrow p6 = &mut s.a; // p6的被借用对象是s.a - // 被借用对象是结构体的某个字段 - struct S s = { .a = 5 }; - int *borrow p6 = &mut s.a; //p6的被借用对象是s.a + // 被借用对象是函数的返回值,与被调用函数的借用类型入参的 “被借用对象” 一样 + int local1 = 10, local2 = 20; + int *borrow p7 = call( + &mut local1, + &mut + local2); //被调用函数call有两个借用类型入参,因此p7的被借用对象是local1和local2 - // 被借用对象是函数的返回值,与被调用函数的借用类型入参的 “被借用对象” 一样 - int local1 = 10, local2 = 20; - int *borrow p7 = call(&mut local1, &mut local2); //被调用函数call有两个借用类型入参,因此p7的被借用对象是local1和local2 + // 被借用对象是全局变量 + int *borrow p8 = &const g; // p8的被借用对象是g - // 被借用对象是全局变量 - int *borrow p8 = &const g; //p8的被借用对象是g + // 被借用对象是函数入参 + int *borrow p9 = &mut a; // p9的被借用对象是a + int *borrow p10 = &mut * b; // p10的被借用对象是*b + int *borrow p11 = &mut * c; // p11的被借用对象是*c + int *borrow p12 = &mut d.a; // p12的被借用对象是d.a +} - // 被借用对象是函数入参 - int *borrow p9 = &mut a; //p9的被借用对象是a - int *borrow p10 = &mut *b; //p10的被借用对象是*b - int *borrow p11 = &mut *c; //p11的被借用对象是*c - int *borrow p12 = &mut d.a; //p12的被借用对象是d.a +int main() { + test(); + return 0; } ``` @@ -2952,24 +3660,32 @@ void test(int a, int *owned b, int *c, struct S d) { 一个变量的生命周期从它的声明开始,到当前整个语句块结束,这个设计被称为Lexical Lifetime,因为变量的生命周期是严格和词法中的作用域范围绑定的。这个策略实现起来非常简单,但它可能过于保守了,某些情况下借用变量的作用范围被过度拉长了,以至于某些实质上是安全的代码也被阻止了,这在一定程度上限制了程序员能编写出的代码。因此,毕昇 C 为借用变量引入 Non-Lexical Lifetime(简写为NLL),用更精细的手段计算借用变量真正起作用的范围,**借用变量的 NLL 范围为:从借用处开始,一直持续到最后一次使用的地方**。具体的,它是**从借用变量定义或被再赋值开始,到被再赋值之前最后一次被使用结束**。 其中,以下场景属于对借用变量p的使用: -1. 函数调用use(p)或use(&mut*p) -2. 函数返回return p或return &mut*p -3. 解引用*p -4. 成员访问p->field +1. 函数调用`use(p)`或`use(&mut *p)` +2. 函数返回`return p`或`return &mut *p` +3. 解引用`*p` +4. 成员访问`p->field` 举例来说: ```C +void use(int *borrow p) {} +void other_op() {} + //本例中p的NLL是分段的,每段NLL都有一个被借用对象 void test() { - int local = 5; //#1 - int *borrow p = &mut local1;//#2,p的第一段NLL开始,被借用对象为local1 - other_op(); //#3 - use(p); //#4,p的第一段NLL结束 - other_op(); //#5 - p = &mut local2; //#6,p的第二段NLL开始,被借用对象为local2,由于后面没有再对p的使用,p的NLL结束 - other_op(); //#7 + int local1 = 1, local2 = 2; //#1 + int *borrow p = &mut local1; //#2,p的第一段NLL开始,被借用对象为local1 + other_op(); //#3 + use(p); //#4,p的第一段NLL结束 + other_op(); //#5 + p = &mut local2; //#6,p的第二段NLL开始,被借用对象为local2,由于后面没有再对p的使用,p的NLL结束 + other_op(); //#7 +} +// p的NLL是:[2,4]->local1, [6,6]->local2 + +int main() { + test(); + return 0; } -//p的NLL是:[2,4]->local1, [6,6]->local2 ``` ##### 2.4 被借用对象的 Lexical Lifetime 与借用变量不同,被借用对象的生命周期是 Lexical Lifetime,对于不同种类被借用对象的生命周期,我们给出具体的定义: @@ -2989,74 +3705,105 @@ void test() { 在2.1中我们提到过,对于借用,我们有这样的生命周期约束:**借用变量的生命周期,不能比被借用对象的生命周期长**。 举例来说: ```C +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +void use(int *borrow p) {} +int *borrow call(int *borrow p, int *borrow q) { return p; } + //本例中,p的生命周期为[2,4],被借用对象local的生命周期为[1,4],满足生命周期约束 void test1() { - int local = 5; //#1 - int *borrow p = &mut local; //#2 - use(p); //#3 -} //#4 + int local = 5; //#1 + int *borrow p = &mut local; //#2 + use(p); //#3 +} //#4 //本例中,p的生命周期有两段“ //第一段为[2,2],被借用对象local的生命周期为[1,8],满足生命周期约束 //第二段为[5,7],被借用对象local1的生命周期为[4,6],不满足生命周期约束,error void test2() { - int local = 5; //#1 - int *borrow p = &mut local; //#2 - { //#3 - int local1 = 5; //#4 - p = &mut local1; //#5 - } //#6 - use(p); //#7 -} //#8 + int local1 = 5; //#1 + int *borrow p = &mut local1; //#2 + { //#3 + int local2 = 5; //#4 + p = &mut local2; //#5 + } //#6 + use(p); //#7 +} //#8 //本例中p的生命周期有两段: //第一段为[2,2],被借用对象local的生命周期为[1, 8],满足生命周期约束 -//第二段为[5,7],被借用对象有两个,分别是local和local1,其中local1的生命周期为[4, 6],不满足生命周期约束,error +//第二段为[5,7],被借用对象有两个,分别是local和local1,其中local1的生命周期为[4, +//6],不满足生命周期约束,error void test3() { - int local = 5; //#1 - int *borrow p = &mut local; //#2 - { //#3 - int local1 = 5; //#4 - p = call(&mut local, &mut local1); //#5 - } //#6 - use(p); //#7 -} //#8 - -//本例中,if分支对p进行了重新赋值,在#10 use(p)时,p的被借用对象local2的生命周期已经结束,不满足生命周期约束,error -//else分支满足生命周期约束 + int local1 = 5; //#1 + int *borrow p = &mut local1; //#2 + { //#3 + int local2 = 5; //#4 + p = call(&mut local1, &mut local2); //#5 + } //#6 + use(p); //#7 +} //#8 + +//本例中,if分支对p进行了重新赋值,在#10 +//use(p)时,p的被借用对象local2的生命周期已经结束,不满足生命周期约束,error +// else分支满足生命周期约束 void test4() { - local = 5; //#1 - int* borrow p = &mut local; //#2 - int local1 = 5; //#3 - if (rand()){ //#4 - int local2 = 5; //#5 - p = &mut local2; //#6 - } else { //#7 - p = &mut local1; //#8 - } //#9 - use(p); //#10 + int local = 5; //#1 + int *borrow p = &mut local; //#2 + int local1 = 5; //#3 + if (rand()) { //#4 + int local2 = 5; //#5 + p = &mut local2; //#6 + } else { //#7 + p = &mut local1; //#8 + } //#9 + use(p); //#10 } //本例中,p的生命周期为[2,4],被借用对象*x的生命周期为[1,3],不满足生命周期约束,error void test5() { - int *owned x = safe_malloc(5); //#1 - int *borrow p = &mut *x; //#2 - safe_free(x); //#3 - use(p); //#4 -} //#5 + int *owned x = safe_malloc(5); //#1 + int *borrow p = &mut * x; //#2 + safe_free((void *owned)x); //#3 + use(p); //#4 +} //#5 + +int main() { + test1(); + test2(); + test3(); + test4(); + test5(); + return 0; +} ``` #### 3.可变借用和不可变借用 毕昇 C 将借用指针的权限进行了分级,分为可变(mut)借用和不可变(immut)借用,我们可以通过操纵可变借用指针来读写被借用对象的内容,通过不可变借用指针,我们只能读取被借用对象的内容,但是不能修改它。例如: ```C +// 可变借用指针类型为 T *borrow void use_mut(int *borrow p) { - *p = 5; //通过可变借用指针,可以修改被借用对象的值 - int a = *p; //通过可变借用指针,可以读取被借用对象的值 + // 通过可变借用指针,可以修改被借用对象的值 + *p = 5; + // 通过可变借用指针,可以读取被借用对象的值 + int a = *p; } +// 不可变借用指针类型为 const T *borrow void use_immut(const int *borrow p) { - *p = 5; //error,无法通过不可变借用指针,来修改被借用对象的值 - int a = *p; //通过不可变借用指针,可以读取被借用对象的值 + // error,无法通过不可变借用指针,来修改被借用对象的值 + *p = 5; + // 通过不可变借用指针,可以读取被借用对象的值 + int a = *p; +} + +int main() { + int i = 1; + int *borrow pmi = &mut i; + use_mut(pmi); + const int *borrow pimi = &const i; + use_immut(pimi); + return 0; } ``` ##### 3.1 `&mut e`要求 e 是可修改的 @@ -3074,47 +3821,65 @@ void use_immut(const int *borrow p) { ##### 3.2 可变借用同时只能存在一个 如果有两个或更多的指针同时访问同一数据,并且至少有一个指针被用来写入数据,可能会导致未定义行为,例如: ```C +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 +#include + +void free_a(int *a) { free(a); } +void read_a(int *a) { printf("%d\n", *a); } + void test() { - int *a = malloc(sizeof(int)); - *a = 42; - int *p1 = a; - int *p2 = a; - free_a(p1); //该函数会释放 a 所指向的内存 - read_a(p2); //该函数会读取 a 所指向的内存 + int *a = malloc(sizeof(int)); + *a = 42; + int *p1 = a; + int *p2 = a; + // 该函数会释放 a 所指向的内存 + free_a(p1); + // 该函数会读取 a 所指向的内存 + read_a(p2); // 打印一个脏值 +} + +int main() { + test(); + return 0; } ``` 由于借用本质上也是指针,所以为了避免上述问题,毕昇 C 规定,**同一时刻,对于同一个对象,要么只能拥有一个可变借用, 要么任意多个不可变借用**。 ```C +void write(int *borrow p) {} +void read(const int *borrow p) {} + void test1() { - int local = 5; - int *borrow p1 = &mut local; - int *borrow p2 = &mut local; //error,同一时刻最多只能有一个指向local的可变借用变量 - modify(p1); - modify(p2); + int local = 1; + int *borrow p1 = &mut local; + // error: 同一时刻最多只能有一个指向local的可变借用变量 + int *borrow p2 = &mut local; + write(p1); + write(p2); } void test2() { - int local = 1; - int *borrow p1 = &mut local; - int *borrow p2 = &mut local; //error,同一时刻最多只能有一个指向local的可变借用变量 - use(p2); - use(p1); + int local = 1; + int *borrow p1 = &mut local; + // error,指向local的可变和不可变借用不能同时存在 + const int *borrow p2 = &const local; + write(p1); + read(p2); } void test3() { - int local = 1; - int * borrow p1 = &mut local; - const int * borrow p2 = &const local; //error,指向local的可变和不可变借用不能同时存在 - use(p1); - use(p2); + int local = 1; + const int *borrow p1 = &const local; + // error: 指向local的可变和不可变借用不能同时存在 + int *borrow p2 = &mut local; + read(p1); + write(p2); } -void test4() { - int local = 1; - const int * borrow p1 = &const local; - int * borrow p2 = &mut local; // error,指向local的可变和不可变借用不能同时存在 - use(p1); - use(p2); +int main() { + test1(); + test2(); + test3(); + return 0; } ``` @@ -3122,37 +3887,47 @@ void test4() { 例如: ```C +#include + struct A { - int *p; + int *p; }; -const int * borrow struct A::get_p(This * borrow this) { - return &const *(this->p); +const int *borrow struct A::get_p(This *borrow this) { + return &const * (this->p); } -void struct A::free_p(This * borrow this) { - free(this->p); -} +void struct A::free_p(This *borrow this) { free(this->p); } int main() { - struct A a = { .p = malloc(sizeof(int)) }; - const int * borrow q = a.get_p(); // q借用了a.p - a.free_p(); // a.p指向的内存被释放 - printf("%d", *q); // *q的操作可能会导致未定义行为 - return 0; + struct A a = {.p = malloc(sizeof(int))}; + // q借用了a.p + const int *borrow q = a.get_p(); + // a.p指向的内存被释放 + a.free_p(); + // *q的操作可能会导致未定义行为 + printf("%d\n", *q); + return 0; } ``` -上述代码中,`a.free_p()`实际上使用了一个指向 a 的可变借用,该可变借用会使在它之前被定义的借用 q 失效,由于`printf("%d", *q)`使用了失效的 q,毕昇 C 编译器会报错,也就阻止了不安全行为的发生。 +上述代码中,`a.free_p()`实际上使用了一个指向 a 的可变借用,该可变借用会使在它之前被定义的借用 q 失效,由于`printf("%d\n", *q)`使用了失效的 q,毕昇 C 编译器会报错,也就阻止了不安全行为的发生。 由于不可变借用不会导致被借用对象被修改,因此同一时刻可以拥有任意多个不可变借用,例如: ```C +void read(const int *borrow p) {} + void test() { - int local = 5; - const int *borrow p1 = &const local; - const int *borrow p2 = &const local; - read(p1); //ok,同一时刻可以拥有任意多个不可变借用 - read(p2); + int local = 5; + const int *borrow p1 = &const local; + const int *borrow p2 = &const local; + read(p1); + read(p2); +} + +int main() { + test(); + return 0; } ``` @@ -3189,21 +3964,27 @@ void test() { 3. 如果函数参数中有多个借用类型的参数,函数返回是借用类型,那么我们直接认为这个返回值的借用,同时包含了从多个借用类型参数传递过来的 “被借用变量”,这个返回的借用也应该满足前面提到的那些借用规则。 ```C -int* borrow f1(int* borrow p); -int* borrow f2(int* borrow p1, int* borrow p2); +int *borrow f1(int *borrow p) { return p; } +int *borrow f2(int *borrow p1, int *borrow p2) { return p1; } void test() { - int local = 5; - int* borrow p1 = f1(&mut local); - /* 函数 f1 的参数创建了一个对 local 的可变借用,这个借用被传递给了返回值 p1, - 导致 p1 相当于是对 local 的一个可变借用, 所以返回值 p1 的被借用对象是 local, - 在 p1 的生命周期结束之前,local 会一直被冻结。*/ + int local = 5; + int *borrow p1 = f1(&mut local); + /* 函数 f1 的参数创建了一个对 local 的可变借用,这个借用被传递给了返回值 p1, + 导致 p1 相当于是对 local 的一个可变借用, 所以返回值 p1 的被借用对象是 + local, 在 p1 的生命周期结束之前,local 会一直被冻结。*/ - int local1, local2; - int* borrow p2 = f2(&mut local1, &mut local2); - /* 函数 f2 的参数创建了一个对 local1 和 local2 的可变借用,这两个借用被传递给了返回值 p2, - 导致 p2 相当于是对 local1 和 local2 的一个可变借用, 所以返回值 p2 的被借用对象是 local1 和 local2, - 在 p2 的生命周期结束之前,local1 和 local2 一直被冻结。*/ + int local1, local2; + int *borrow p2 = f2(&mut local1, &mut local2); + /* 函数 f2 的参数创建了一个对 local1 和 local2 + 的可变借用,这两个借用被传递给了返回值 p2, 导致 p2 相当于是对 local1 和 + local2 的一个可变借用, 所以返回值 p2 的被借用对象是 local1 和 local2, 在 p2 + 的生命周期结束之前,local1 和 local2 一直被冻结。*/ +} + +int main() { + test(); + return 0; } ``` @@ -3212,25 +3993,32 @@ void test() { 2. 结构体内如果包含多个借用成员,那么这个结构体同时存在多个 “被借用对象”,这些借用成员也应该满足前面提到的那些借用规则。 ```C +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + struct R { - int* borrow m1; - int* borrow m2; + int *borrow m1; + int *borrow m2; }; void test() { - int local1, local2; - struct R r = { .m1 = &mut local1, .m2 = &mut local2 }; - // 在 r 的生命周期结束之前,local1 和 local2 一直被冻结。 - // 因为变量 r 在初始化的时候创建了一个对 local1 和 local2 的可变借用, - // 导致 r 同时包含对 local1 的一个可变借用,也包含对 local2 的可变借用。 + int local1, local2; + struct R r = {.m1 = &mut local1, .m2 = &mut local2}; + // 在 r 的生命周期结束之前,local1 和 local2 一直被冻结。 + // 因为变量 r 在初始化的时候创建了一个对 local1 和 local2 的可变借用, + // 导致 r 同时包含对 local1 的一个可变借用,也包含对 local2 的可变借用。 +} + +int main() { + test(); + return 0; } ``` #### 7. 借用变量的解引用操作 -允许对借用指针变量解引用,与标准 C 的解引用操作一致:对借用指针变量 p 解引用的语法为 `*p`。 -对 const T * borrow 类型的借用变量 e 解引用 `*e`,结果为 T 类型 -对 T * borrow 类型的借用变量 e 解引用 `*e`,结果为 T 类型 -如果 p 是指向 T 类型的借用,o 是 T 类型的 lvalue,对于 `*p` 表达式,有如下限制: +允许对借用指针变量解引用,与标准 C 的解引用操作一致:对借用指针变量`p`解引用的语法为 `*p`。 +对`const T * borrow`类型的借用变量`e`解引用 `*e`,结果为`T`类型 +对`T * borrow`类型的借用变量`e`解引用 `*e`,结果为`T`类型 +如果`p`是指向`T`类型的借用,`o`是`T`类型的 lvalue,对于`*p`表达式,有如下限制: | | T是Copy语义 | T是Move语义 | | ---- | ---- | ---- | | p是immut借用 | *p = expr; 不允许 | *p = expr; 不允许 | @@ -3238,16 +4026,16 @@ void test() { | p是mut借用 | *p = expr; 允许 | *p = expr; 允许 | | | o = *p; 允许 | o = *p; 不允许| -上表中 move / copy 语义分别指: T 是 owned 修饰的类型和 T 是其它类型。 +上表中 move / copy 语义分别指: `T`是`owned`修饰的类型和`T`是其它类型。 注:上表中的赋值操作的权限,可同样应用于函数的传参和返回场景。 #### 8. 借用变量的成员访问 -允许借用指针变量访问成员变量或调用成员函数,与标准 C 的箭头运算符一致:访问指针变量 p 的成员变量 field 的语法为 `p->field`,调用指针变量 p 的成员方法 method() 的语法为 `p->method()`。 +允许借用指针变量访问成员变量或调用成员函数,与标准 C 的箭头运算符一致:访问指针变量`p`的成员变量`field`的语法为`p->field`,调用指针变量`p`的成员方法`method()`的语法为`p->method()`。 ##### 8.1 访问成员变量 -通过借用访问成员变量,表达式的类型取决于成员变量本身的类型。`p->field` 表达式的类型与 field 成员定义的类型相同。 -如果 `p->field` 的类型是 T,o 是 T 类型的 lvalue,对于 `p->field` 表达式,有如下限制: +通过借用访问成员变量,表达式的类型取决于成员变量本身的类型。`p->field` 表达式的类型与`field`成员定义的类型相同。 +如果 `p->field` 的类型是`T`,`o`是`T`类型的 lvalue,对于 `p->field` 表达式,有如下限制: | | T是Copy语义 | T是Move语义 | | ---- | ---- | ---- | | p是immut借用 | p->field = expr; 不允许 | p->field = expr; 不允许 | @@ -3262,55 +4050,85 @@ void test() { ##### 8.2 调用成员函数 通过借用调用成员函数,即 `p->method()` 的场景,实参和形参之间的规则如下: -| | void method(const This * borrow this) | void method(This * borrow this) | -| ---- | ---- | --- | -| p是immut借用 | 允许 | 不允许,immut借用不能创建出mut借用 | -| p是mut借用 | 允许,允许从mut借用创建immut借用 | 允许 | +| | void method(const This * borrow this) | void method(This * borrow this) | | +| --------- | ------------------------------------- | ------------------------------- | --- | +| p是immut借用 | 允许 | 不允许,immut借用不能创建出mut借用 | | +| p是mut借用 | 允许,允许从mut借用创建immut借用 | 允许 | | 举例来说: ```C -void int::method1(const This * borrow this) {} -void int::method2(This * borrow this) {} +void int ::method1(const This *borrow this) {} +void int ::method2(This *borrow this) {} void test() { - int local = 5; - const int *borrow p1 = &const local; - int *borrow p2 = &mut local; - p1->method1(); //ok, 形参类型和实参类型一致,都是不可变借用 - p1->method2(); //error, 形参是可变借用类型,实参是不可变借用类型,不可变借用不能创建出可变借用 - p2->method1(); //ok, 形参是不可变借用类型,实参是可变借用类型,允许从可变借用创建出不可变借用 - p2->method2(); //ok, 形参类型和实参类型一致,都是可变借用 + int local = 5; + const int *borrow p1 = &const local; + int *borrow p2 = &mut local; + // 形参类型和实参类型一致,都是不可变借用 + p1->method1(); + // error: 形参是可变借用类型,实参是不可变借用类型,不可变借用不能创建出可变借用 + p1->method2(); + // 形参是不可变借用类型,实参是可变借用类型,允许从可变借用创建出不可变借用 + p2->method1(); + // 形参类型和实参类型一致,都是可变借用 + p2->method2(); +} + +int main() { + test(); + return 0; } ``` #### 9. 借用的类型转换 1. 对于任意类型 T,如果 T 实现了 trait TR,则允许指向类型 T 的借用向上转型为指向类型 TR 的借用;反过来,从类型 TR 的借用往类型 T 借用的转换,是不允许的。 ```C -trait T { - void print(This* borrow this); -}; -void int::print(int* this) { } +#include -impl trait T for int; +trait TR { void print(This * borrow this); }; +void int ::print(int *this) { printf("%d\n", *this); } + +impl trait TR for int; void test() { - int x = 10; - int * borrow r = &mut x; - trait T * borrow p = r; //支持 int* 类型的借用向上转型为 trait TR* 类型的借用 - p->print(); + int x = 10; + int *borrow r = &mut x; + //支持 int* 类型的借用向上转型为 trait TR* 类型的借用 + trait TR *borrow p = r; + p->print(); + // error: 禁止 trait TR* 向下转型 + int *borrow px = (int *borrow)p; +} + +int main() { + test(); + return 0; } ``` 2. 允许指向类型 T 的借用转换为指向 void 类型的借用,反过来从类型 void 的借用往类型 T 借用的转换,是不允许的。 ```C void test() { - int x = 10; - int * borrow r = &mut x; - void * borrow p = r; //支持 int * 类型的借用转为 void * 类型的借用 + int x = 10; + int *borrow r = &mut x; + void *borrow p = r; + // error: 不允许转为 void *borrow 类型 + int *borrow t = (int *borrow)p; +} + +int main() { + test(); + return 0; } ``` -3. 只允许在非安全区进行 T * borrow 和 T * 之间的转换。 +3. 只允许在非安全区进行`T * borrow`和`T *`之间的转换。 ```C -unsafe { int * borrow p = (int* borrow) NULL }; +int main() { + // 非安全区允许 T * borrow 和 T * 之间的转换 + int *borrow p = (int *borrow)NULL; + // 安全区禁止 T * borrow 和 T * 之间的转换 + safe { int *borrow p = (int *borrow)NULL; } + return 0; +} ``` #### 10. 借用的其它规则 @@ -3321,475 +4139,158 @@ unsafe { int * borrow p = (int* borrow) NULL }; 2. 借用变量在定义的时候必须初始化。 ```C void test() { - int *borrow p; //error + // error: 必须初始化 + int *borrow p; +} + +int main() { + test(); + return 0; } ``` -3. 用一个借用类型的表达式给另外一个借用类型的 lvalue作初始化或再赋值,即 p = e,p 和 e 必须是同类型的借用类型,而且要求 e 的生命周期必须大于 p 的生命周期。 +3. 用一个借用类型的表达式给另外一个借用类型的 lvalue作初始化或再赋值,即`p = e`,`p`和`e`必须是同类型的借用类型,而且要求`e`的生命周期必须大于 p 的生命周期。 ```C +#include + void test() { - const int *borrow p = &mut local; //error + int x = 1; + int *borrow p = &mut x; + { + int y = 2; + int *borrow pp = &mut y; + // error: pp 生命周期小于 p + p = pp; + printf("%d\n", *p); + } + printf("%d\n", *p); +} + +int main() { + test(); + return 0; } ``` -基于此规则,一个 struct 内部的 borrow 指针成员,是不可以对这个 struct 或者它的其它成员做借用的。 +基于此规则,一个`struct`内部的`borrow`指针成员,是不可以对这个`struct`或者它的其它成员做借用的。 ```C struct S { - int m; - const int * borrow p; + int m; + const int *borrow p; }; void test() { - struct S s = { .m = 0, .p = &const s.m }; // error,因为s.p的生命周期与s.m的生命周期相同 + // error: 因为s.p的生命周期与s.m的生命周期相同 + struct S s = {.m = 0, .p = &const s.m}; +} + +int main() { + test(); + return 0; } ``` 4. 借用变量不允许是全局变量,只能是局部变量。 ```C +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + int g = 5; -int *borrow p = &mut g; //error -void test() { - int *borrow p = &mut g; //ok +// error: 借用变量不允许是全局变量 +int *borrow p = &mut g; +void test() { int *borrow p = &mut g; } + +int main() { + test(); + return 0; } ``` -5. 不允许对包含借用的表达式,再取借用。同理,借用类型 T* borrow 中,T 本身及其成员不能是借用类型。 +5. 不允许对包含借用的表达式,再取借用。同理,借用类型`T* borrow`中,`T`本身及其成员不能是借用类型。 ```C +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + struct R { - int *borrow p; + int *borrow p; }; void test() { - int local = 5; - int *borrow *borrow p = &mut (&mut local); //error + int local = 5; + // error: 不允许多级借用指针 + int *borrow *borrow p = &mut(&mut local); + + struct R r1 = {.p = &mut local}; + // error: r1 中已经包含了借用 + struct R *borrow r2 = &mut r1; +} - struct R r1 = { .p = &mut local }; - struct R * borrow r2 = &mut r1; //error,r中已经包含了借用 +int main() { + test(); + return 0; } ``` 6. 不允许为借用类型实现 trait。 ```C -impl trait TR for int *borrow;//error +trait TR{}; + +// error: 不允许为借用类型实现 trait +impl trait TR for int *borrow; + +int main() { return 0; } ``` 7. 不允许为借用类型添加成员函数。 ```C -void int *borrow::f() {} //error +// error: 不允许为借用类型添加成员函数 +void int *borrow::f() {} + +int main() { return 0; } ``` 8. union 的成员不允许是借用类型。 ```C -union MyUnion { - int *borrow p;//error,借用指针不允许作为union成员 +// error: 借用指针不允许作为 union 成员 +union U { + int *borrow p; }; -``` - -9. 借用指针类型不能是泛型实参。 - -10. 借用指针变量不支持索引运算。 - -11. 借用指针变量不支持算术运算。 - -12. 允许同类型的借用变量之间,使用 `==`、`!=`、`>`、`<`、`<=`、`>=` 等比较运算符操作。 - -13. 允许对借用类型使用 `sizeof`、`alignof`操作符,并且有: -sizeof(T* borrow) == sizeof(T*) -_Alignof(T* borrow) == _Alignof(T*) - -14. 允许对借用类型使用一元的`&`、`!`及二元的`&&`、`||`运算符。 - -15. 不允许对借用类型使用一元的`-`、`~`、`&const`、`&mut`、`[]`、`++`、`--`运算符,也不允许对借用类型使用二元的`*`、`/`、`%`、`&`、`|`、`<<`、`>>`、`+`、`-`运算符。 - -16. 如果一个借用指针变量指向的是函数,那么可以通过这个借用指针变量来调用函数。 -```C -int f() {} -void test() { - int (* borrow p)() = &mut f; - p(); -} -``` - -17. 不允许对函数做可变借用,只能做只读借用。 - - -### 安全区 - -#### 概述: - -c 语言有很多规则过于灵活,不方便编译器做静态检查。因此我们引入一个新语法,使得在一定范围内的毕昇 c 代码必须遵循更严格的约束,保证在这个范围内的代码肯定不会出现“内存安全”问题。 - -允许用 safe/unsafe 关键字修饰函数、语句、括号表达式。 - -- ​ unsafe 表示这段代码在非安全区,这部分代码遵循标准 c 的规定,同时这部分代码的安全性由用户保证。 - - -- ​ safe 表示这段代码在安全区,这部分代码必须遵循更严格的约束,同时编译器可以保证内存安全。 - - -- ​ 没有 safe/unsafe 关键字修饰的全局函数默认是非安全的。 - -#### 代码示例: - -```c -#include - -typedef struct File{ -} FileID; - -FileID* owned create(void) { - FileID *p = malloc(sizeof(FileID)); - return (FileID* owned)p; -} - -FileID* owned consume_and_return(FileID* owned p) { - return p; -} -// 使用 safe 修饰函数,表示该函数为安全函数,函数内为安全区 -safe void safe_free(FileID* owned p) { - // 使用 unsafe 修饰代码块,表示这段代码在非安全区。这段代码是在安全区内的非安全区,也属于非安全区 - unsafe {free((FileID*)p);} -} - -int main(void) { - FileID* owned p1 = create(); - FileID* owned p2 = consume_and_return(p1); - // 使用 safe 修饰语句,表示这段代码在安全区 - safe safe_free(p2); - return 0; -} +int main() { return 0; } ``` -#### 语法规则: - -1. 允许使用 safe/unsafe 修饰函数声明、函数签名、函数定义、函数指针、语句、括号表达式。 - - ```c - // 修饰函数签名 - safe int test(int, int); - // 修饰函数定义 - safe int test(int a, int b); - // 修饰函数声明 - safe int test(int a, int b) { - return a + b; - } - // 修饰函数指针 - safe int (*p)(int, int); - - safe int main(void) { - safe { // 修饰代码块 - int a = 1; - } - unsafe { - int b = 1; - } - // 修饰语句 - safe int c = 1; - unsafe c++; - // 修饰括号表达式 - char d = unsafe((char)c); - return 0; - } - ``` - -2. 不允许使用 safe/unsafe 修饰全局变量、函数外类型声明、typedef 声明(允许修饰函数指针)。 - - ```c - #include - - safe int g_a; // error: 不允许修饰全局变量 - safe struct b { // error: 不允许修饰函数外类型声明 - int a; - }; - safe typedef int mm; // error: 不允许修饰 typedef - safe typedef int (*p)(int a); // ok: 允许修饰函数指针 - int main() { - return 0; - } - ``` - -3. safe 修饰的函数,参数类型和返回类型必须是 safe 类型。 - - 非 safe 类型包括:裸指针类型、union 类型、成员中包含不安全类型的 struct 类型、成员中包含不安全类型的数组类型。 - - ```c - safe int* test1(int a); // error: 返回值为非安全类型的裸指针类型 - safe int test2(int *a); // error: 参数类型为非安全类型的裸指针类型 - - typedef struct F { - int* a; - } SF; - safe SF test3(int a); // error: 返回值为成员中包含不安全裸指针类型的 struct 类型 - safe int test4(SF b); // error: 参数类型为成员中包含不安全裸指针类型的 struct 类型 - ``` - -4. safe 修饰的函数,函数参数列表不可以省略。`safe void test(); `是不允许的, `safe void test(void); `是允许的。 - -5. safe 修饰的函数,函数参数列表不可以包含变长参数。`safe int test(int a, ...); `是不允许的。 - -6. 如果 trait 中的函数被声明为 safe,那么要求实现 trait 的类型的对应成员函数也必须是 safe 修饰的函数。若 trait 中的函数未声明为 safe,也允许实现 trait 中的类型的成员函数为 safe。 - - ```c - trait G { - safe int* owned test1(This* owned this); - int* owned test2(This* owned this); - }; - safe int* owned int::test1(int* owned this) { - return this; - } - safe int* owned int::test2(int* owned this) { - return this; - } - impl trait G for int; - ``` - -7. 多个同名函数声明必须有同样的 safe/unsafe 修饰。 - - ```c - safe int test(int a); - unsafe int test(int a); // error: 多个函数声明中 safe/unsafe 不一致 - ``` - -8. 多个同名函数声明排除 safe/unsafe 修饰后,是完全一致的。 - - ```c - safe int test(int a); - safe int test(int a, int b); // error: 函数声明不完全一致 - ``` - -9. safe 修饰泛型函数时,会对泛型每个实例化版本也做 safe 检查。 - - ```c - safe T test(T a) { - return a; - } - - void test2() { - int a = 1; - int b = test(a); - int* owned c = (int* owned)&a; - int* owned d = test(c); - int* e = test((void*)0); // error: 实列化函数入参和返回值为非安全的裸指针类型 - } - ``` - -10. 成员函数也可以被 safe/unsafe 修饰,其规则和全局函数一样。 - - ```c - struct MyStruct { - T res; - }; - safe T struct MyStruct::foo_a(T a) { - return a; - } - ``` - -11. 对于 safe 修饰的函数指针类型,与赋值的函数参数和返回值类型必须是一致的。 - - ```c - safe void test1(int a) {} - safe void test2(void) {} - safe void (*p)(int a); - int main() { - p = test1; // ok - p = test2; // error: 参数类型不一致,不允许赋值 - } - ``` - -12. 安全区内被调用的函数或函数指针必须是 safe 的函数签名,不允许调用非安全函数或函数指针。 - - ```c - safe void test1(void) {} - unsafe void test2(void) {} - safe void (*test3)(void); - unsafe void (*test4)(void); - int main() { - safe { - test1(); - test2(); // error: 安全区内不允许调用非安全函数 - test3(); - test4(); // error: 安全区内不允许调用非安全函数指针 - } - unsafe { - test1(); - test2(); - test3(); - test4(); - } - } - ``` - -13. 安全区内允许再包含 unsafe 修饰的语句、函数指针、括号表达式,非安全区内也允许再包含 safe 修饰的语句、函数指针、括号表达式。 - - ```c - int test1(int a, int b) { - return a + b; - } - safe int test2(int a, int b) { - return a > b ? a : b; - } - int main() { - safe { - int a = 0; - unsafe a++; - unsafe { - a = test1(1, 3); - safe a = test2(3, 5); - } - } - } - ``` - -15. 安全区内不允许无初始化或初始化不完整的变量声明。 - - ```c - struct F { - int age; - char name[20]; - }; - void test() { - safe { - int a; // error: 安全区内不允许无初始化的变量声明 - struct F tom = {10}; // error:安全区不允许部分初始化 - struct F tony = {10, "tony"}; - } - } - ``` - -16. 安全区内 switch 语句中的 case/default 只能存在于 switch 后面的第一层代码块中,且第一层代码块不允许有变量定义。 - - ```c - safe void test(int a) { - switch (a) { - int b = 10; // error: 第一层代码块不允许有变量定义 - case 0: { - int c = 1; // ok - break; - } - { - case 1: { // error: case 只能存在于 switch 后面的第一层代码块中 - break; - } - } - { - default:{ // error: default 只能存在于 switch 后面的第一层代码块中 - break; - } - } - } - } - ``` - -17. 安全区内不允许使用自增 (++)、自减(--)操作符,不允许 union 类型通过“.”访问成员,不允许裸指针通过“->”访问成员, - - 允许owned指针和borrow指针通过“->”访问成员。 - - ```c - union un { - int age; - char name[16]; - }; - struct F { - int age; - }; +9. 借用指针类型不能是泛型实参。 - void test(void) { - struct F d = {10}; - struct F *e = &d; - struct F* owned f = (struct F* owned)&d; - struct F* borrow i = &mut d; - safe { - int a = 1; - a++; // error: 安全区不允许自增 - a--; // error: 安全区不允许自减 - union un b = {10}; - int c = b.age; // error: 安全区不允许 union 通过“.”访问成员 - int g = e->age; // error: 安全区不允许裸指针通过“->”访问成员 - int h = f->age; // ok :允许owned指针“->”通过访问成员 - int j = i->age; // ok :允许borrow指针“->”通过访问成员 - } - } - ``` +10. 借用指针变量不支持索引运算。 -18. 安全区内不允许使用取地址符“&”(允许对函数取地址),只允许&const,&mut取借用。 +11. 借用指针变量不支持算术运算。 - 安全区内不允许解引用裸指针类型,但可以解引用owend指针类型和borrow指针类型。 +12. 允许同类型的借用变量之间,使用 `==`、`!=`、`>`、`<`、`<=`、`>=` 等比较运算符操作。 - ```c - #include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 - - void test() { - safe { - int a = 10; - int *b = &a; // error: 安全区不允许取地址符号 - int c = *b; // error: 安全区不允许解引用裸指针 - int * owned d = safe_malloc(2); - int e = *d; // ok: 解引用owned指针 - safe_free(d); - int * borrow f = &mut a; - int g = *f; // ok: 解引用borrow指针 - } - } - ``` +13. 允许对借用类型使用 `sizeof`、`alignof`操作符,并且有: +`sizeof(T* borrow) == sizeof(T*)` +`_Alignof(T* borrow) == _Alignof(T*)` -19. 安全区内不允许指向类型不同的指针类型之间转换,不允许指针和非指针类型之间的转换,不允许 owned/borrow/raw 指针之间的转换。 +14. 允许对借用类型使用一元的`&`、`!`及二元的`&&`、`||`运算符。 - ```c - void test() { - int *a; - double *b; - safe { - b = a; // error:不允许指向类型不同的指针类型之间转换 - a = b; // error:不允许指向类型不同的指针类型之间转换 - b = (double *)a; // error:不允许指向类型不同的指针类型之间转换 - } - int c; - safe { - a = c; // error:不允许指针和非指针类型之间的转换 - c = a; // error:不允许指针和非指针类型之间的转换 - } - int* owned d = (int* owned)&c; - safe { - a = d; // error:不允许 owned/raw 指针之间的转换 - d = a; // error:不允许 owned/raw 指针之间的转换 - } - } - ``` +15. 不允许对借用类型使用一元的`-`、`~`、`&const`、`&mut`、`[]`、`++`、`--`运算符,也不允许对借用类型使用二元的`*`、`/`、`%`、`&`、`|`、`<<`、`>>`、`+`、`-`运算符。 -20. 安全区内不允许表达范围从大向小的类型转换(比如从 long 转换为 int,从 int 转换为 _Bool,从 int 转换为 enum)。不允许表达精度从高向低的类型转换(比如从 double 转换为 float)。对于基础类型的常量发生类型转换,如果目标类型可以描述这个值,那么该类型转换是允许的。 +16. 如果一个借用指针变量指向的是函数,那么可以通过这个借用指针变量来调用函数。 +```C +#include - ```c - void test() { - long a; - int b; - _Bool c; - double e; - float f; - safe { - a = b; // ok - b = a; // error: 不允许表达范围从大向小的类型转换 - a = 1; // ok - c = 1; // ok - c = 2; // error: 目标类型不可以描述这个值,不允许转换 - e = f; // ok - f = e; // error:不允许表达精度从高向低的类型转换 - f = 1.0; // ok - f = 1.2f; // ok - } - } - ``` +void f() { printf("f()\n"); } -21. 安全区内不允许内嵌汇编语句。 +void test() { + // 只能对函数取不可变借用 + void (*borrow const p)() = &const f; + p(); +} - ```c - void test() { - safe { - int ret = 0; - int src = 1; - asm("move %0, %1\n\t" :"=r"(ret) :"r"(src)); // error: 安全区不允许内嵌汇编 - } - } - ``` +int main() { + test(); + return 0; +} +``` + +17. 不允许对函数做可变借用,只能做只读借用。 ## owned struct 类型 @@ -3799,57 +4300,80 @@ int main(void) { `owned struct` 类型的定义由关键字 `owned struct` 和自定义的名字组成,紧跟着是定义在一对花括号中 `owned struct` 定义体,在定义体中可以定义成员变量、析构函数、成员函数和访问修饰符。允许定义泛型 `owned struct`。 示例: ```c -owned struct Person{ +#include + +owned struct Person { public: - char name[50]; - int age; - char* getName(Person *this) { - return this->name; - } - int getAge(Person *this) { - return this->age; - } - ~Person(Person this) { - printf("Name = %s\n", this.getName()); - printf("Age = %d\n", this.getAge()); - } + char name[50]; + int age; + char *getName(Person *this) { return this->name; } + int getAge(Person *this) { return this->age; } + ~Person(Person this) { + printf("Name = %s\n", this.getName()); + printf("Age = %d\n", this.getAge()); + } }; + +int main() { + Person p = {.name = "Tom", .age = 18}; + return 0; +} ``` 上例定义了名为 `Person` 的 `owned struct` 类型,它有两个成员变量 `name` 和 `age`、成员函数`getName` 和 `getAge`、以及析构函数 `~Person`。 注:`owned struct` 与 `C struct`类似,允许结构体嵌套。 #### 成员变量 -`owned struct` 的成员变量分为实例成员变量和静态成员变量(`static` 修饰),和 `struct` 一样,成员变量定义的时候不允许写初始化。 +`owned struct` 的成员变量分为实例成员变量和静态成员变量(由`static` 修饰),和 `struct` 一样,成员变量定义的时候不允许写初始化。 ```c +#include + owned struct Person{ public: char name[50]; static int age; }; -int Person::age = 20; + +int main() { + Person::age = 18; + printf("%d\n", Person::age); + return 0; +} ``` #### 析构函数 在一个对象生命周期结束时,会自动调用析构函数。析构函数与 `owned struct` 类型同名,前面带有 `~`, 但是不允许写泛型参数。当用户未定义析构函数时,编译器会自动提供默认的析构函数。 ```c +#include + owned struct S { - ~S(S this) { - printf("S destructor\n"); - } + ~S(S this) { printf("S destructor\n"); } }; + +int main() { + S s = {}; + return 0; +} ``` 当`owned struct`内有owned 修饰的指针的时候,该指针需要在析构函数内手动释放 ```c -owned struct Person{ +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 +#include + +owned struct Person { public: - int * owned age; - ~Person(Person this){ - safe_free((void * owned) this.p); - } + int *owned age; + ~Person(Person this) { + printf("%d\n", *this.age); + safe_free((void *owned)this.age); + } }; -Person p = {.p = safe_malloc(42)}; + +int main() { + Person p = {.age = safe_malloc(18)}; + return 0; +} ``` @@ -3865,28 +4389,33 @@ Person p = {.p = safe_malloc(42)}; + 先调用外层类型的析构函数,再调用成员变量的析构函数 + 不保证各个成员的析构函数的调用顺序 + `owned struct` 及其内部的成员变量,在作用域结束时,必须处于以下两种状态之一 - + owned 状态:`owned struct` 及其内部所有owned修饰的成员(1.owned struct 也属于owned 修饰的成员 2.递归包含)均未被移动 - + moved 状态:`owned struct` 作为一个整体已被显式移动 -+ `owned struct` 及其内部的成员变量,在作用域结束时,如若处于如下状态,则报错。 报错信息模板为 partially moved `owned struct`:`%0` at scope end, `%1` moved"(%0 为`owned struct` 变量名,%1 为被移动的成员变量名) - + 有成员变量被转移所有权的状态: `owned struct`(递归包含嵌套成员)至少有一个owned 修饰的成员被移动。结构体本身未被整体移动。 -+ `owned struct`内owned 修饰的指针需要在析构函数内被手动释放。如未被手动释放,则报错。 报错模板信息为 destructor for`%0` incorrect, %1 of owned type and needs to be handled manually( %0 为owned struct 变量名) + + `owned` 状态:`owned struct` 及其内部所有`owned`修饰的成员(1.`owned struct` 也属于`owned` 修饰的成员 2.递归包含)均未被移动 + + `moved` 状态:`owned struct` 作为一个整体已被显式移动 ++ `owned struct` 及其内部的成员变量,在作用域结束时,如若处于如下状态,则报错。 报错信息模板为 partially moved `owned struct`:`%0` at scope end, `%1` moved"(`%0` 为`owned struct` 变量名,`%1` 为被移动的成员变量名) + 1. 有成员变量被转移所有权的状态: `owned struct`(递归包含嵌套成员)至少有一个`owned` 修饰的成员被移动。结构体本身未被整体移动。 ++ `owned struct`内 `owned` 修饰的指针需要在析构函数内被手动释放。如未被手动释放,则报错。 报错模板信息为 destructor for`%0` incorrect, %1 of owned type and needs to be handled manually( `%0` 为 `owned struct` 变量名) #### 成员函数 `owned struct` 的成员函数分为实例成员函数和静态成员函数(不允许 `static` 修饰)。实例成员函数需要显式参数 `this`, 假设当前 `owned struct` 类型是 `C`, `this` 的类型可以是 `C*`、`const C*`、`C* borrow`、`const C * borrow`、`C * owned`。静态成员函数是没有 `this`。成员函数的访问与 `struct` 扩展成员函数访问一样(详见成员函数章节)。 -注:当前不支持 owned struct 内部定义泛型函数。 +注:当前不支持 `owned struct` 内部定义泛型函数。 ```c -owned struct Person{ +#include + +owned struct Person { public: - char name[50]; - int age; - char* getName(Person *this) { - return this->name; - } - char* getTypeName(){ - return "Preson"; - } + char name[50]; + int age; + char *getName(Person *this) { return this->name; } + char *getTypeName() { return "Preson"; } }; + +int main() { + Person p = {.name = "Tom", .age = 18}; + printf("TypeName: %s\nInstance Name: %s\n", Person::getTypeName(), + p.getName()); + return 0; +} ``` 上例是 `owned struct` 定义体内部实例成员函数 `getName` 和静态成员函数 `getTypeName`。 @@ -3895,19 +4424,23 @@ public: 示例: ```c -owned struct Person{ -public: - char name[50]; - int age; - char* getName(Person *this); - char* getTypeName(); +#include +owned struct Person { +public: + char name[50]; + int age; + char *getName(Person *this); + char *getTypeName(); }; -char* Person::getName(Person *this) { - return this->name; -} -char* Person::getTypeName(){ - return "Preson"; +char *Person::getName(Person *this) { return this->name; } +char *Person::getTypeName() { return "Preson"; } + +int main() { + Person p = {.name = "Tom", .age = 18}; + printf("TypeName: %s\nInstance Name: %s\n", Person::getTypeName(), + p.getName()); + return 0; } ``` `owned struct` 与 `struct` 类似,允许扩展成员函数(详见成员函数章节)。 @@ -3930,7 +4463,7 @@ int A::f(A* this) { ``` ### 创建 `owned struct`实例 -`owned struct` 允许使用 `struct initializer` 语法创建实例,也允许单独对每个成员变量初始化(如果成员变量是 `public`, 此时与 `struct` 一样单独跟踪每个成员的初始化状态,但是需要在安全区状态下保证在发生 `move`、传参、析构和返回等场景下该变量一定已经完整初始化, 非安全区不做保证。同时为了方便 `owned struct` 类型在使用的时候不携带 `owned struct` 关键字。 +`owned struct` 允许使用 `struct initializer` 语法创建实例,也允许单独对每个成员变量初始化(如果成员变量是 `public`, 此时与 `struct` 一样单独跟踪每个成员的初始化状态,但是需要在安全区状态下保证在发生 `move`、传参、析构和返回等场景下该变量一定已经完整初始化, 非安全区不做保证。同时为了方便 **`owned struct` 类型在声明、定义时不携带 `owned struct` 关键字**。 ## 运算符重载 @@ -3940,33 +4473,33 @@ int A::f(A* this) { ### 代码示例 -```c++ +```c #include -struct square -{ - int width; - int height; + +struct square { + int width; + int height; }; // 运算符‘+’号重载函数 -__attribute__((operator +)) -struct square squareAdd(struct square s1, struct square s2){ - struct square s = {s1.width + s2.width, s1.height + s2.height}; - return s; +__attribute__((operator+)) struct square squareAdd(struct square s1, + struct square s2) { + struct square s = {s1.width + s2.width, s1.height + s2.height}; + return s; } -int main(){ - struct square s1 = {100, 50}; - struct square s2 = {60, 110}; - // 在之前我们必须主动调用函数来完成结构体运算操作。 - struct square s3 = squareAdd(s1, s2); - assert(s3.width == 160); - assert(s3.height == 160); - // 将函数加上重载标记后,现在我们可以直接对该结构体进行运算操作。 - struct square s4 = s1 + s2; - assert(s4.width == 160); - assert(s4.height == 160); - return 0; +int main() { + struct square s1 = {100, 50}; + struct square s2 = {60, 110}; + // 在之前我们必须主动调用函数来完成结构体运算操作。 + struct square s3 = squareAdd(s1, s2); + assert(s3.width == 160); + assert(s3.height == 160); + // 将函数加上重载标记后,现在我们可以直接对该结构体进行运算操作。 + struct square s4 = s1 + s2; + assert(s4.width == 160); + assert(s4.height == 160); + return 0; } ``` @@ -4003,131 +4536,143 @@ struct square squareAdd(struct square s1, struct square s2){ 4、运算符重载函数入参和返回值要求。 -| 运算符 | 入参要求 | 返回值要求 | -| -------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -| 关系运算符 | 只允许有两个入参, 且至少有一个参数是用户自定义类型,如结构体、枚举。 | 返回值类型必须是_Bool类型 | +| 运算符 | 入参要求 | 返回值要求 | +| -------------------------- | -------------------------------------------------- | --------------------------------------- | +| 关系运算符 | 只允许有两个入参, 且至少有一个参数是用户自定义类型,如结构体、枚举。 | 返回值类型必须是_Bool类型 | | *(解引用),
-> (成员访问运算符) | 只允许有一个入参,且第一个参数为用户自定义类型的指针类型,包括裸指针、可变借用指针、不可变借用指针。 | 返回值类型必须是指针类型,包括裸指针、可变借用指针、不可变借用指针、Rc指针。 | -| [] (索引运算符) | 只允许有两个入参,且第一个参数为用户自定义类型的指针类型,包括裸指针、可变借用指针、不可变借用指针。 | 返回值类型必须是指针类型,包括裸指针、可变借用指针、不可变借用指针、Rc指针。 | -| 其他 | 单目运算符只允许有一个入参, 双目运算符值只允许有两个入参。函数至少有一个入参是用户自定义类型。 | 无 | +| [] (索引运算符) | 只允许有两个入参,且第一个参数为用户自定义类型的指针类型,包括裸指针、可变借用指针、不可变借用指针。 | 返回值类型必须是指针类型,包括裸指针、可变借用指针、不可变借用指针、Rc指针。 | +| 其他 | 单目运算符只允许有一个入参, 双目运算符值只允许有两个入参。函数至少有一个入参是用户自定义类型。 | 无 | - 关系运算符重载代码示例 - ```c - #include - struct Foo { - int x; - int y; - }; +```c +#include - __attribute__((operator >)) - _Bool FooCompare(struct Foo A, struct Foo B) { // 参数至少有一个是用户自定义类型 - return A.y > B.y; - }; +struct Foo { + int x; + int y; +}; - int main() { - struct Foo f1 = {180, 18}; - struct Foo f2 = {166, 22}; - assert(f2 > f1 && "compare error"); - return 0; - } - ``` +// 参数至少有一个是用户自定义类型 +__attribute__((operator>)) +_Bool FooCompare(struct Foo A, struct Foo B) { + return A.y > B.y; +}; + +int main() { + struct Foo f1 = {180, 18}; + struct Foo f2 = {166, 22}; + assert(f2 > f1 && "compare error"); + return 0; +} +``` - 解引用运算符重载代码示例 - ```c - #include - struct MyPoint { - T * data; - }; - - __attribute__((operator *)) - T * derefMyPoint(struct MyPoint * borrow p) { // 入参和返回值必须是指针类型。 - return p->data; - } - - int main() { - int data = 100; - struct MyPoint p = {&data}; - // 编译器匹配到重载函数后,自动对p取地址并传入重载函数,并对函数返回值做解引用。此处等效为(*derefMypoint(&mut p)) == 100。 - assert(*p == 100); - *p = 10; - assert(*p == 10); - return 0; - } - ``` +```c +#include + +struct MyPoint { + T *data; +}; + +// 入参和返回值必须是指针类型 +__attribute__((operator*)) +T * derefMyPoint(struct MyPoint *borrow p) { + return p->data; +} + +int main() { + int data = 100; + struct MyPoint p = { + &data + }; + // 编译器匹配到重载函数后,自动对p取地址并传入重载函数,并对函数返回值做解引用。此处等效为(*derefMypoint(&mut p)) == 100 + assert(*p == 100); + *p = 10; + assert(*p == 10); + return 0; +} +``` - 成员访问运算符重载代码示例 - ```c - #include - struct MyData { - T a; - }; - struct MyPoint { - MyData* data; - }; - - __attribute__((operator ->)) - MyData * mDerefMyPoint(struct MyPoint * borrow p) { // 入参和返回值必须是指针类型。 - return p->data; - } - - int main() { - struct MyData d = {100}; - struct MyPoint p = {&d}; - assert(p->a == 100); // 编译器匹配到重载函数后,自动对p取地址并传入重载函数。此处等效为mDerefMyPoint(&mut p)->a == 100。 - p->a = 10; - assert(p->a == 10); - return 0; - } - ``` +```c +#include + +struct MyData { + T a; +}; +struct MyPoint { + MyData *data; +}; + +// 入参和返回值必须是指针类型 +__attribute__((operator->)) +MyData * mDerefMyPoint(struct MyPoint *borrow p) { + return p->data; +} + +int main() { + struct MyData d = { + 100 + }; + struct MyPoint p = { + &d + }; + // 编译器匹配到重载函数后,自动对p取地址并传入重载函数。此处等效为mDerefMyPoint(&mut p)->a == 100 + assert(p->a == 100); + p->a = 10; + assert(p->a == 10); + return 0; +} +``` - 索引运算符重载代码示例 - ```c - #include - #define ARRLEN 10 - struct MyArray { - T data[ARRLEN]; - }; - - __attribute__((operator [])) - T * GetMyArrayData(struct MyArray * p, int index) { - return &p->data[index]; - } - - int main() { - struct MyArray array; - for (int i = 0; i < ARRLEN; i++) { - // 编译器匹配到重载函数后,自动对p取地址并传入重载函数,并对函数返回值做解引用。此处等效为*GetMyArrayData(&mut p, i) = i。 - array[i] = i; - } - assert(array[5] == 5); - return 0; - } - ``` +```c +#include +#define ARRLEN 10 + +struct MyArray { + T data[ARRLEN]; +}; + +__attribute__((operator[])) +T *GetMyArrayData(struct MyArray *p, int index) { + return &p->data[index]; +} + +int main() { + struct MyArray array; + for (int i = 0; i < ARRLEN; i++) { + // 编译器匹配到重载函数后,自动对p取地址并传入重载函数,并对函数返回值做解引用。此处等效为*GetMyArrayData(&mut p, i) = i + array[i] = i; + } + assert(array[5] == 5); + return 0; +} +``` - 运算符重载错误代码示例 - ```c - // error: 不允许定义无用户自定义入参类型的运算符重载函数。 - __attribute__((operator +)) - int squareAdd(int a, int b){ - return a + b; - } - ``` +```c +// error: 不允许定义无用户自定义入参类型的运算符重载函数。 +__attribute__((operator+)) +int squareAdd(int a, int b) { return a + b; } +``` 5、运算符重载函数名不能与普通函数名冲突。对同一个运算符定义多个重载函数时,如果函数名不同并且参数类型不完全一致,允许同时存在。 ```c __attribute__((operator +)) -struct square squareAdd(struct square s1, struct square s2){ - /* code */ +struct square squareAdd(struct square s1, struct square s2) { + /* code */ } // 支持对同一个运算符多次重载 __attribute__((operator +)) -struct oblong oblongAdd(struct oblong s1, struct oblong s2){ - /* code */ +struct oblong oblongAdd(struct oblong s1, struct oblong s2) { + /* code */ } ``` @@ -4136,45 +4681,59 @@ struct oblong oblongAdd(struct oblong s1, struct oblong s2){ ```c #include -struct Point -{ - T x; - T y; +struct Point { + T x; + T y; }; -__attribute__((operator +)) -struct Point Add(struct Point lhs, struct Point rhs){ +__attribute__((operator+)) +struct Point Add(struct Point lhs, struct Point rhs) { T x1 = lhs.x + rhs.x; - T y1 = lhs.y + rhs.y; - struct Point p = {.x = x1, .y = y1}; + T y1 = lhs.y + rhs.y; + struct Point p = { + .x = x1, .y = y1 + }; return p; } -__attribute__((operator *)) -struct Point Mul(struct Point lhs, struct Point rhs){ - T x1 = lhs.x * rhs.x; - T y1 = lhs.y * rhs.y; - struct Point p = {.x = x1, .y = y1}; +__attribute__((operator*)) struct Point +Mul(struct Point lhs, struct Point rhs) { + T x1 = lhs.x * rhs.x; + T y1 = lhs.y * rhs.y; + struct Point p = { + .x = x1, .y = y1 + }; return p; } void test1() { - struct Point p1 = {.x = 1, .y = 2}; - struct Point p2 = {.x = 3, .y = 4}; - struct Point p3 = p1 + p2; // {.x = 4, .y = 6} - printf("p3.x: %d, p3.y :%d\n", p3.x, p3.y); + struct Point p1 = { + .x = 1, .y = 2 + }; + struct Point p2 = { + .x = 3, .y = 4 + }; + // {.x = 4, .y = 6} + struct Point p3 = p1 + p2; + printf("p3.x: %d, p3.y :%d\n", p3.x, p3.y); } void test2() { - struct Point p1 = {.x = 1, .y = 2}; - struct Point p2 = {.x = 3, .y = 4}; - struct Point p3 = p1 * p2; // {.x = 3, .y = 8} - printf("p3.x: %d, p3.y :%d\n", p3.x, p3.y); + struct Point p1 = { + .x = 1, .y = 2 + }; + struct Point p2 = { + .x = 3, .y = 4 + }; + // {.x = 3, .y = 8} + struct Point p3 = p1 * p2; + printf("p3.x: %d, p3.y :%d\n", p3.x, p3.y); } int main() { test1(); test2(); + return 0; } ``` @@ -4191,48 +4750,52 @@ BSC 不允许在安全区内使用`NULL`,但是在日常开发中,空指针 我们用一个简单的例子来学习如何定义指针的 nullabiliy 并使用它: ```C -#include "bishengc_safety.hbs" +#include "bishengc_safety.hbs" // BiShengC 语言提供的头文件,用于安全地进行内存分配及释放 + +// 获取当前运行状态的函数 +safe int get_current_status(void) { + unsafe { return rand() % 2; } +} -safe int get_current_status(void); // 获取当前运行状态的函数 +safe void read_data(T *borrow p) {} struct Data { - int value; + int value; }; // 如果 init 成功,返回具体的地址,否则返回 nullptr safe struct Data *owned _Nullable init_data(void) { - if (get_current_status() == 1) { - struct Data data = { 10 }; - struct Data *owned p = safe_malloc(data); - return p; - } - return nullptr; + if (get_current_status() == 1) { + struct Data data = {10}; + struct Data *owned p = safe_malloc(data); + return p; + } + return nullptr; } -safe void read_data(struct Data *borrow p); - safe int main(void) { - // 使用 _Nullable 修饰指针类型: - struct Data *owned _Nullable p = init_data(); - // init 后 p 可能为空指针,因此需要先判空再使用: - if (p != nullptr) { - read_data(&mut *p); // 如果没有对 p 做判空,编译器会报 error! - safe_free((void *owned)p); - } - return 0; + // 使用 _Nullable 修饰指针类型: + struct Data *owned _Nullable p = init_data(); + // init 后 p 可能为空指针,因此需要先判空再使用: + if (p != nullptr) { + // 如果没有对 p 做判空,编译器会报 error! + read_data(&mut * p); + safe_free((void *owned)p); + } + return 0; } ``` ### 指针变量的 Nullability 指针变量的默认 Nullability 跟 qualifier 和指针类型有关: 1. 如果有 qualifier(`_Nonnull`或`_Nullable`)修饰,以这个为准 -2. 如果没有 qualifier 修饰,裸指针默认是 Nullable 的,owned 和 borrow 指针默认是 Nonnull 的 +2. 如果没有 qualifier 修饰,裸指针默认是 Nullable 的,`owned` 和 `borrow` 指针默认是 Nonnull 的 ```C // Nullable 指针: -int *_Nullable p1 = nullptr; +int *_Nullable p1 = nullptr; int *borrow _Nullable p2 = nullptr; int *owned _Nullable p3 = nullptr; -int * p4 = nullptr; +int *p4 = nullptr; // Nonnull 指针: int *_Nonnull p5 = &a; int *borrow _Nonnull p6 = &mut a; @@ -4247,82 +4810,122 @@ int *owned p9 = safe_malloc(5); 2. 如果在控制流语句中对其做了判空,那么在非空的分支中,可以把它当做 Nonnull 指针使用 ```C -safe int *borrow _Nullable nullable(int* borrow p); //返回值类型为 Nullable 的 -safe int *borrow nonnull(int* borrow p); //返回值类型为 Nonnull 的 +// 返回值类型为 Nullable +safe T *borrow _Nullable nullable(T *borrow p) { return p; } +// 返回值类型为 Nonnull +safe T *borrow nonnull(T *borrow p) { return p; } safe void test(void) { - int local = 10; - int *borrow _Nullable p1 = nullptr; // p1 初始化后 Nullability 为 Nullable - *p1 = 10; // error - p1 = &mut local; // p1 被再赋值后 Nullability 变为 Nonnull - *p1 = 20; // ok - p1 = nullable(&mut local); // p1 被再赋值后 Nullability 变为 Nullable - *p1 = 20; // error - p1 = nonnull(&mut local); // p1 被再赋值后 Nullability 变为 Nonnull - *p1 = 20; // ok + int local = 10; + int *borrow _Nullable p1 = nullptr; // p1 初始化后 Nullability 为 Nullable + *p1 = 10; // error + p1 = &mut local; // p1 被再赋值后 Nullability 变为 Nonnull + *p1 = 20; // ok + p1 = nullable(&mut local); // p1 被再赋值后 Nullability 变为 Nullable + *p1 = 20; // error + p1 = nonnull(&mut local); // p1 被再赋值后 Nullability 变为 Nonnull + *p1 = 20; // ok + + int *borrow _Nullable p2 = nullable(&mut local); // Nullable + if (p2 != nullptr) // if 分支中 p2 的 Nullability 为 Nonnull + *p2 = 10; // ok + else // else 分支中 p2 的 Nullability 为 Nullable + *p2 = 20; // error +} - int *borrow _Nullable p2 = nullable(&mut local); // Nullable - if (p2 != nullptr) // if 分支中 p2 的 Nullability 为 Nonnull - *p2 = 10; // ok - else // else 分支中 p2 的 Nullability 为 Nullable - *p2 = 20; // error +int main() { + test(); + return 0; } ``` ### 指针的赋值、传参和返回 1. 不允许用可空表达式给 Nonnull 指针赋值: ```C -safe int *borrow _Nullable nullable(int* borrow p); //返回值类型为 Nullable -safe int *borrow nonnull(int* borrow p); //返回值类型为 Nonnull +// 返回值类型为 Nullable +safe T *borrow _Nullable nullable(T *borrow p) { return p; } +// 返回值类型为 Nonnull +safe T *borrow nonnull(T *borrow p) { return p; } safe void test(void) { - int local = 10; - int *borrow p1 = nullptr; // error - int *borrow p2 = nullable(&mut local); // error - int *borrow p3 = nonnull(&mut local); // ok + int local = 10; + // error: 安全区内禁止用 nullable 值给 nonnull 指针赋值 + int *borrow p1 = nullptr; + // error: 安全区内禁止用 nullable 值给 nonnull 指针赋值 + int *borrow p2 = nullable(&mut local); + int *borrow p3 = nonnull(&mut local); // ok - int *borrow _Nullable p4 = nullable(&mut local); // p4 的 Nullability 是 Nullable - int *borrow p5 = p4; // error + int *borrow _Nullable p4 = nullable(&mut local); + // error: 安全区内禁止用 nullable 值给 nonnull 指针赋值 + int *borrow p5 = p4; - int *borrow _Nullable p6 = nonnull(&mut local); // p6 的 Nullability 是 Nonnull - int *borrow p7 = p6; // ok + int *borrow _Nullable p6 = nonnull(&mut local); + int *borrow p7 = p6; +} + +int main() { + test(); + return 0; } ``` 2. 函数调用时,如果形参为 Nonnull 类型,那么不能使用用可空表达式作为实参: ```C -safe void take_nonnull(int *borrow p); // 接收 Nonnull 类型的指针作为参数 -safe int *borrow _Nullable nullable(int* borrow p); // 返回值类型为 Nullable +// 返回值类型为 Nullable +safe T *borrow _Nullable nullable(T *borrow p) { return p; } + +// 接收 Nonnull 类型的指针作为参数 +safe void take_nonnull(int *borrow p) {} safe void test(void) { - int local = 10; - int *borrow _Nullable p = nullable(&mut local); - take_nonnull(p); // error + int local = 10; + int *borrow _Nullable p = nullable(&mut local); + // error: 要求 nonnull 指针,但实参是 nullable + take_nonnull(p); +} + +int main() { + test(); + return 0; } ``` 3. 函数返回值类型如果是 Nonnull 类型,也不能使用可空表达式作为返回值: ```C safe int *borrow return_nonnull(int *borrow p) { - int *borrow _Nullable q = nullptr; - return q; // error + int *borrow _Nullable q = nullptr; + // error: 要求返回指针是 nonnull + return q; +} +int main() { + int i = 1; + int *borrow pi = return_nonnull(&mut i); + return 0; } ``` ### 指针的解引用、成员访问 被定义为 Nullable 的指针的 Nullability 会随赋值和控制流发生变化,BSC 编译器会跟踪这些变换,保证指针解引用、成员访问等操作的安全性。 ```C +// 返回值类型为 Nullable +safe T *borrow _Nullable nullable(T *borrow p) { return p; } + struct Data { - int value; + int value; }; -safe struct Data *borrow _Nullable nullable(struct Data *borrow); safe void test(void) { - struct Data data = { .value = 10 }; - struct Data *borrow _Nullable p = nullable(&mut data); - if (p != nullptr) { - p->value = 10; // ok,如果没有判空操作,编译器会报 error - } + struct Data data = {.value = 10}; + struct Data *borrow _Nullable p = nullable(&mut data); + if (p != nullptr) { + // 经过判空后, nullable 会转换为 nonnull + p->value = 10; + } +} + +int main() { + test(); + return 0; } ``` @@ -4331,34 +4934,48 @@ safe void test(void) { 1. 如果是通过初始化列表进行初始化,BSC 编译器会根据初始化表达式来初始化 Nullable 指针成员的 Nullability 2. 对于其它初始化方式,直接认为 Nullable 指针成员的 Nullability 为 Nullable 的,这可能会导致本身没有问题的代码无法通过编译,此时需要对 Nullable 指针成员作再赋值,或使用判空语句,即可改变 Nullability。 ```C -struct Data { int *borrow _Nullable value; }; +// 返回值类型为 Nonnull +safe T *borrow nonnull(T *borrow p) { return p; } -safe struct Data init_data(int* borrow p); -safe int *borrow nonnull(int* borrow p); +struct Data { + int *borrow _Nullable value; +}; + +safe struct Data init_data(int *borrow p) { + struct Data d = {.value = p}; + return d; +} safe void test(void) { - int local = 10; - // 使用初始化列表做初始化: - struct Data data1 = { .value = nonnull(&mut local) };// data1.value 的 Nullability 是 Nonnull - *data1.value = 10; // ok - // 使用函数返回值做初始化: - struct Data data2 = init_data(&mut local); // data2.value 的 Nullability 是 Nullable - *data2.value = 10; // error - // 使用变量赋值做初始化: - struct Data data3 = data1; // data3.value 的 Nullability 是 Nullable - *data3.value = 10; // error - - // 对 Nullable 指针成员作再赋值,可以改变 Nullability: - data2.value = nonnull(&mut local); - *data2.value = 10; // ok - // 通过指针判空语句,也可以改变 Nullability: - if (data3.value != nullptr) - *data2.value = 10; // ok + int local = 10; + // 使用初始化列表做初始化: 推断为 nonnull + struct Data data1 = {.value = nonnull(&mut local)}; + *data1.value = 10; + // 使用函数返回值做初始化: 无法推断 nullability 默认为 nullable + struct Data data2 = init_data(&mut local); + // error: 直接使用 nullable 指针 + *data2.value = 10; + // 使用变量赋值做初始化: 无法推断 nullability 默认为 nullable + struct Data data3 = data1; + // error: 直接使用 nullable 指针 + *data3.value = 10; + + // 对 Nullable 指针成员作再赋值,可以改变 Nullability: + data2.value = nonnull(&mut local); + *data2.value = 10; + // 通过指针判空语句,也可以改变 Nullability: + if (data3.value != nullptr) + *data2.value = 10; +} + +int main() { + test(); + return 0; } ``` ### 非空指针检查的范围和控制选项 -非空指针检查是一项强大的功能,能帮助开发者在编译期识别出潜在的危险行为。同时,这会带来一定的编译性能开销和编码行为限制。默认情况下,对非空指针的检查仅在安全区生效,安全区的定义详见[内存安全-安全区](#安全区)章节。 +非空指针检查是一项强大的功能,能帮助开发者在编译期识别出潜在的危险行为。同时,这会带来一定的编译性能开销和编码行为限制。**默认情况下,对非空指针的检查仅在安全区生效**,安全区的定义详见[内存安全-安全区](#安全区)章节。 在非安全区,我们将是否开启非空指针检查的选择权交给开发者,即通过编译选项`-nullability-check=value`来控制该项检查的作用域。其中`value`是一个枚举值,有3种选项`none`, `onlysafe`, `all`: @@ -4369,15 +4986,18 @@ safe void test(void) { 提供一个示例说明: ```C -safe void nullptrTest(void) { +safe void test(void) { int *borrow _Nullable p = nullptr; - { - unsafe { - *p = 10; // error1: nullable pointer cannot be dereferenced - } + unsafe { + *p = 10; // error1: nullable pointer cannot be dereferenced } *p = 5; // error2: nullable pointer cannot be dereferenced } + +int main() { + test(); + return 0; +} ``` 对于上面这个示例,当编译选项`-nullability-check`不存在或者`-nullability-check=safeonly`时,只有在`safe`区的`error2`会被报告;当`-nullability-check=none`时,`error1`和`error2`均不会被报告;当`-nullability-check=all`时,非安全区的`error1`和安全区`error2`均会被报告。