7.5 KiB
03 - 变量声明与赋值
Viper 的变量声明使用 Python 的类型注解语法,通过注解指定 C 级别的类型信息。
类型书写规范:优先用短名别名(
int/uchar/uint64/bool…)、T*指针、前缀数组[N]T,尽量少用|。详见 02-type-system.md 类型书写规范。
带注解的变量声明
使用 变量名: 类型 语法声明变量:
x: int = 0 # int x = 0;
y: uint32 = 42 # uint32_t y = 42;
name: [64]char # char name[64];
ptr: void* # void* ptr;
msg: const char* = "Hello" # const char* msg = "Hello";
无初始值的声明
模块级(全局)无初值声明生成零初始化的全局变量(LLVM zeroinitializer,零初始化成立):
count: int # int count = 0;(zeroinitializer;仅模块级成立)
buffer: [256]char # char buffer[256] = {0};
函数内局部无初值声明仅分配空间、不初始化——值未定义,勿假定为 0,最好预先赋值或用 memset 清除:
tmp: int # 函数内局部:仅分配未初始化(值未定义,勿假定为 0)
延迟赋值
变量可以先声明后赋值:
x: int
x = 10
指针变量
p: int* # int* p;
fb: void* # void* fb;
str_ptr: const char* # const char* str_ptr;
指针变量的特殊初始化
指针判空可直接赋 None:
p: void* = None # void* p = NULL;
旧的显式写法 t.CVoid(0, t.CPtr) 仍可用,但不推荐。
普通赋值
x: int = 0
x = 42 # x = 42;
增强赋值
支持所有 Python 增强赋值运算符:
x: int = 0
x += 1 # x += 1;
x -= 1 # x -= 1;
x *= 2 # x *= 2;
x //= 3 # x /= 3; (整数除法)
x %= 5 # x %= 5;
x <<= 1 # x <<= 1;
x >>= 1 # x >>= 1;
x |= 0xFF # x |= 0xFF;
x &= 0x0F # x &= 0x0F;
x ^= 0xAA # x ^= 0xAA;
指针读写
指针的取地址 / 解引用用 & / *(取代 c.Addr / c.Deref / c.DerefAs / c.Load):
v: int = 42
p: int* = &v # 取地址
*p = 7 # 解引用写入(≡ c.DerefAs)
w: int = *p # 解引用读取(≡ c.Deref)
*q = *p # 内存拷贝(≡ c.Load)
volatile 限定
volatile 是软关键字,用于变量声明 / 赋值 / 值位置的调用前缀,使对应的 load / store 落 LLVM volatile:
volatile flag: int = 0 # volatile int flag;
flag = 1 # volatile store
v: int = volatile f() # 值位置:调用结果槽 volatile 存取
LLVM 的
call指令本身没有 volatile 限定符,因此 volatile 语义只落在内存存取上:构造会逐字段store volatile,并在__new__/__before_init__/__init__调用后追加一次 volatile 屏障。
全局变量
模块顶层声明的变量即为全局变量:
kbd_tid: int = -1 # 全局 int kbd_tid = -1;
BootInfo: bootinfo* # 全局 struct bootinfo* BootInfo;
静态全局变量
static x: int = 0 # static int x = 0;(前置软关键字,推荐)
y: t.CStatic | t.CInt = 0 # 等价链式标记写法
static 符号仅限定义模块内可见:跨模块取值(mod.x)、调用(mod.f())、from mod import x 三种形态都会编译期 NameError fail-fast(定义侧登记 + 引用侧拦截)。旧写法 x: static t.CInt(static 写在注解位置)已不再支持,会明确报错。
外部声明
extern errno: int # extern int errno;(前置软关键字,推荐)
fd: t.CExtern | int # 等价链式标记写法(内部标记)
模块级 extern 全局生成 @裸名 = external global T(external linkage、无初始值、无 storage),符号由外部(C 库 / 汇编 / 其它翻译单元)提供;不允许带初始值。
export 也可用于变量(裸名导出、可带初始值):
export tss_buf: [104]uint8 # 裸名导出全局(无初始值时零初始化)
重复声明规则(硬约定)
模块级已有同名变量时,函数内再次用类型注解声明同名变量一律 fail-fast(TypeError,判据不含类型是否一致):
count: int = 0
def f() -> void:
count: int = 1 # ❌ TypeError:count 已是模块级变量,未声明 global 时不得再用注解声明同名变量
两种情形都报错:
- 无
global/nonlocal:会把全局遮蔽成局部 → 重复声明; - 有
global/nonlocal后再用注解重新声明 → 同样重复声明。
合法写法只有两种:
count: int = 0
def f() -> void:
global count
count = 1 # ✅ 修改全局:global + 无注解赋值
- 修改全局:函数内
global x+ 无注解赋值x = v; - 独立局部:换一个不与全局重名的变量名。
仅函数内生效;模块级自身的重复注解不走此检查。
另:读全局变量无需
global,仅读取不报错;写全局且未声明global才报错。
常量定义
编译期常量推荐用 define 软关键字(等价旧写法 X: t.CDefine = Y):
define MAX_SIZE = 1024 # 推荐:自动推断底层类型
define PAGE_SIZE: uint32 = 4096 # 带显式底层类型
MAX_SIZE2: t.CDefine = 1024 # 等价旧写法
t.CDefine 不是数据类型,而是编译时元指令——常量在 LLVM IR 中不生成全局变量,而是在引用处直接内联展开。详见 README.MD 中 t.CDefine 深度解析。
define有两种形态:带=(define X: T = Y/define X = Y)是编译期常量;带((define F(...) -> T)是宏函数。详见 04-functions.md 宏函数。
字符串常量
Viper 中的字符串字面量编译为 C 的字符串常量(const char*):
msg: const char* = "Hello, World!"
serial.puts(msg)
也可以直接传递字符串字面量:
serial.puts("Hello, World!")
类型转换
使用类型构造函数进行显式类型转换:
x: int = 42
y: uint32 = uint32(x) # (uint32_t)x
f: float = float(x) # (float)x
i: int = int(f) # (int)f
指针类型转换
ptr: void* = some_ptr
int_val: uint64 = uint64(ptr) # (uint64_t)ptr — 指针转整数(需显式)
隐式转换不允许指针 ↔ 整数(唯一豁免编译期常量
0);显式强转可以。
结构体实例化
此处不理解参见 05-classes.md 中 结构体实例化与初始化
直接使用类名作为构造函数:
info: fat32_types.fat32_fileinfo
dp: fat32_types.fat32_dirobj
带初始化的实例化:
obj: MyClass = MyClass(arg1, arg2)
使用 & 获取实例指针:
obj_ptr: MyClass* = &MyClass(arg1, arg2)
delete 语句
del 语句可用于调用对象的 __del__ 或 __delete__ 方法:
del obj # 调用 obj.__del__() 或 obj.__delete__()
del arr[idx] # 调用 arr.__delitem__(idx)