12 KiB
08 - C 语言操作
Viper 通过 c 模块提供对 C 语言底层特性的访问,包括指针操作、内联汇编、预处理指令等。
书写规范:
c.*正在被逐步淘汰——指针操作用&/*,内联汇编用__asm__,内联 LLVM IR 用__llvmir__,操作数标记用asminp/asmout/irinp/irout。旧的c.Addr/c.Asm/c.LLVMIR等在过渡期仍可用。
指针操作
指针操作推荐使用 & / * 算子(c.Addr / c.Deref / c.DerefAs / c.Load 仍可用,属过渡期兼容写法)。
| 旧写法 | 新写法 | 说明 |
|---|---|---|
c.Addr(x) |
&x |
取地址,结果类型为 T* |
c.Deref(p) |
*p |
解引用(按 pointee 类型 load) |
c.DerefAs(p, v) |
*p = v |
解引用写入 |
c.Load(p, q) |
*p = *q |
内存拷贝(两侧解引用) |
取地址 &
x: int = 42
p: int* = &x # int* p = &x;(&x 已是 T*,无需再写 t.CPtr 强转)
对结构体成员取地址:
res: int = fat32.opendir("/", &dp) # res = fat32_opendir("/", &dp);
对数组取地址:
viperlib.snprintf(&buf, 64, "hello %d", 42) # snprintf(&buf, 64, "hello %d", 42);
解引用 *
ptr: Sheet* = &sheet_obj
obj: Sheet = *ptr # struct Sheet obj = *ptr;
解引用写入 *p = v 与内存拷贝 *p = *q
*p = value # c.DerefAs 的等价新写法
*p = *q # c.Load 的等价新写法
c.Set(target, value)(target = value)没有独立新语法,直接写普通赋值即可。
内联汇编 __asm__ / c.Asm
Viper 提供了声明式的内联汇编语法,编译为 GCC 风格的 __asm__ __volatile__ 语句。
__asm__(...) 是 c.Asm(...) 的语法糖(裸名调用,无需 import c),两者完全等价:
- 操作数标记
asminp/asmout等价于c.AsmInp/c.AsmOut(裸名,无需import c) - 约束常量可直接写
ASM_DESCR.XXX,等价于t.ASM_DESCR.XXX(无需import t)
旧写法 c.Asm / c.AsmInp / c.AsmOut / t.ASM_DESCR.XXX 全部保留可用。
基本用法
__asm__("nop") # __asm__ __volatile__("nop");
带操作数的汇编
使用 f-string 和 asmout / asminp 标记操作数:
saved_rdi: t.CUnsignedLong
__asm__(f"mov {asmout(saved_rdi, ASM_DESCR.OUTPUT_REG)}, rdi",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RDI])
等价 C 代码:
__asm__ __volatile__(
"mov %0, rdi"
: "=r"(saved_rdi)
:
: "memory", "rdi"
);
输入操作数 asminp
msg: const char* = "Hello"
__asm__(f"mov rdi, {asminp(msg, ASM_DESCR.REG_ANY)}",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX])
完整示例
__asm__(f"""mov rdi, {asminp(msg, ASM_DESCR.REG_ANY)}
call {asminp(log_info_fn, ASM_DESCR.REG_ANY)}""",
op=[ASM_DESCR.CLOBBER_MEMORY, ASM_DESCR.CLOBBER_RAX,
ASM_DESCR.CLOBBER_RCX, ASM_DESCR.CLOBBER_RDX,
ASM_DESCR.CLOBBER_RDI, ASM_DESCR.CLOBBER_RSI,
ASM_DESCR.CLOBBER_R8, ASM_DESCR.CLOBBER_R9,
ASM_DESCR.CLOBBER_R10, ASM_DESCR.CLOBBER_R11])
端口 I/O 示例
def outb(port: uint16, value: uint8):
__asm__(f"""mov edx, {asminp(uint32(port), ASM_DESCR.REG_ANY)}
mov eax, {asminp(uint32(value), ASM_DESCR.REG_ANY)}
out dx, al""",
op=[ASM_DESCR.CLOBBER_RDX, ASM_DESCR.CLOBBER_RAX])
ASM_DESCR 约束字符
下表为
ASM_DESCR(即t.ASM_DESCR)的完整期望常量集;TPV 实际已映射子集见本节末。 常量既可写ASM_DESCR.XXX,也可写t.ASM_DESCR.XXX。
操作数修饰符
| 常量 | 值 | 说明 |
|---|---|---|
MODIFIER_OUTPUT |
= |
输出操作数 |
MODIFIER_READWRITE |
+ |
读写操作数 |
MODIFIER_INPUT |
`` | 输入操作数(默认) |
MODIFIER_GLOBAL |
& |
全局操作数 |
寄存器约束
| 常量 | 值 | 说明 |
|---|---|---|
REG_ANY |
r |
任何通用寄存器 |
REG_EAX / REG_RAX |
a |
EAX/RAX |
REG_EBX / REG_RBX |
b |
EBX/RBX |
REG_ECX / REG_RCX |
c |
ECX/RCX |
REG_EDX / REG_RDX |
d |
EDX/RDX |
REG_ESI / REG_RSI |
S |
ESI/RSI |
REG_EDI / REG_RDI |
D |
EDI/RDI |
REG_XMM |
x |
XMM 寄存器 |
内存与立即数
| 常量 | 值 | 说明 |
|---|---|---|
MEMORY |
m |
内存操作数 |
IMMEDIATE |
i |
立即数 |
ANY |
g |
通用寄存器/内存/立即数 |
预定义组合约束
| 常量 | 值 | 说明 |
|---|---|---|
OUTPUT_REG |
=r |
输出,通用寄存器 |
OUTPUT_MEM |
=m |
输出,内存 |
OUTPUT_EAX |
=a |
输出,EAX/RAX |
INPUT_REG |
r |
输入,通用寄存器 |
INPUT_MEM |
m |
输入,内存 |
INPUT_IMM |
i |
输入,立即数 |
破坏描述符
| 常量 | 说明 |
|---|---|
CLOBBER_EAX / CLOBBER_RAX |
破坏 EAX/RAX |
CLOBBER_EBX / CLOBBER_RBX |
破坏 EBX/RBX |
CLOBBER_ECX / CLOBBER_RCX |
破坏 ECX/RCX |
CLOBBER_EDX / CLOBBER_RDX |
破坏 EDX/RDX |
CLOBBER_ESI / CLOBBER_RSI |
破坏 ESI/RSI |
CLOBBER_EDI / CLOBBER_RDI |
破坏 EDI/RDI |
CLOBBER_CC |
破坏条件码(标志寄存器) |
CLOBBER_MEMORY |
破坏内存 |
CLOBBER_R8 ~ CLOBBER_R15 |
破坏 R8~R15 |
TPV 实际已映射子集:
REG_ANY/REG_EAX..RDI/RAX..R15、OUTPUT_REG/OUTPUT_MEM/OUTPUT_EAX、INPUT_REG/INPUT_MEM/INPUT_EAX…、CLOBBER_MEMORY/CLOBBER_CC/CLOBBER_RAX..R15。REG_XMM/MEMORY/IMMEDIATE/ANY/MODIFIER_OUTPUT/READWRITE/INPUT/GLOBAL等期望实现。
预处理指令
Viper 通过 c 模块的函数调用实现 C 预处理指令。
#define
define MAX_SIZE = 1024 # #define MAX_SIZE 1024(推荐)
define X = Y / define X: T = Y 是推荐的宏定义写法(等价旧写法 X: t.CDefine = Y)。
调用式
c.CDefine("X", v)不存在(期望)。
define 宏函数
define 软关键字与 def f(...) -> t.CDefine 声明的函数均为宏函数:存根保留完整函数体,调用点在编译期内联展开。
TPC 已实现;TransPyV 的展开尚未对齐。
条件编译
编译期条件编译的真实形态是 if 缩进块(已实现),块结束即 #endif:
if c.CIfdef(DEBUG):
... # #ifdef DEBUG
if c.CIfndef(HEADER_H):
... # #ifndef HEADER_H
if c.CIf(VERSION > 2):
... # #if VERSION > 2
语句式
c.CEndif()/c.CElif(...)/c.CElse()不存在,且暂不实现。
#undef
期望实现——当前不存在
c.Undef。
c.Undef(MACRO_NAME) # #undef MACRO_NAME
#error
期望实现——当前不存在
c.CError。
c.CError("Platform not supported") # #error "Platform not supported"
#pragma
此关键字已不再支持
c.CPragma("GCC diagnostic push") # #pragma GCC diagnostic push
## 连接符
此方法已不再支持(属实)
c.TokenPast("PREFIX_", "NAME") # PREFIX_ ## NAME
由于编译器设计,以及不需要像 C 一样做大量字符替换,对于上述所有的条件宏都远比 C 差,实际工程中不推荐使用条件宏,后期将完善宏的设计,引入模板等。
FFI 外部函数声明
Viper 的 FFI(Foreign Function Interface)通过 extern / export 关键字声明,无需旧版注解:
extern def foo() -> T(函数体为pass):仅声明。编译器只生成declare原型,不生成define函数体,由链接器在外部库中解析符号;export def foo() -> T:以裸名导出函数定义。
extern/export是语言层软关键字,解析器会把它们改写为返回注解中的存储类标记(extern→T | t.State,export→T | t.CExport)。t.State/t.CExtern/t.CExport标记并未废弃,仍是这些关键字与extern全局变量的内部承载。
基本声明
extern def 仅声明(函数体为 pass);export def 以裸名导出:
extern def isr0() -> t.CVoid: pass # extern void isr0(); — 来自汇编或外部
extern def getchar() -> t.CInt: pass # extern int getchar(); — 来自 libc
export def foo() -> t.CInt: pass # export int foo(); — 裸名导出(define)
编译为 LLVM IR:
declare void @"isr0"() ; extern:不加 SHA1 前缀,仅 declare
declare i32 @"getchar"()
define i32 @"foo"() { ... } ; export:不加 SHA1 前缀,裸名 define
Win32 API 绑定示例
includes/w32 中的 Win32 绑定以 extern 关键字声明,零特殊语法:
extern def CreateFileA(lpFileName: LPCSTR, dwDesiredAccess: ULONG,
dwShareMode: ULONG,
lpSecurityAttributes: SECURITY_ATTRIBUTES | t.CPtr,
dwCreationDisposition: ULONG,
dwFlagsAndAttributes: ULONG,
hTemplateFile: HANDLE) -> HANDLE:
pass
等价 C 声明:
HANDLE CreateFileA(LPCSTR, ULONG, ULONG, SECURITY_ATTRIBUTES*,
ULONG, ULONG, HANDLE);
链接时通过 project.json 指定外部库(如 kernel32.lib、user32.lib),链接器解析这些符号。
修饰方式
| 写法 | SHA1 前缀 | 生成内容 | 用途 |
|---|---|---|---|
| 无修饰(普通函数) | ✅ 加前缀 | declare + define |
模块私有函数 |
extern def f() |
❌ 不加 | 仅 declare |
FFI 外部函数声明(引用外部) |
export def f() |
❌ 不加 | 裸名 define |
对外导出 API |
新关键字等价写法:无修饰 = 普通函数加 SHA1 前缀;
extern= 仅 declare;export= 裸名 define。旧的t.CExtern/t.CExport/t.State标记在内部仍承载这些关键字,手写可用但不推荐。
extern重名(临时规则,待修正):多个模块声明同名extern符号时,调用点按依赖 / 导入顺序取「最近」的那个实体;请避免多模块重复声明同名extern,该行为日后会修正。详见 10-imports.md 的跨模块约束。
声明外部全局变量
外部全局变量用 extern 软关键字声明(推荐),或等价的 t.CExtern 标记:
extern errno: t.CInt # extern int errno;(推荐)
fd: t.CExtern | t.CInt # 等价写法
模块级 extern 全局生成 @裸名 = external global T(external linkage、无初始值、无 storage),符号由外部单元提供。
详见 02-type-system.md 的 t.State 章节 和 includes/w32 的 Win32 绑定实现。
LLVM IR 内联
__llvmir__ / c.LLVMIR
__llvmir__(...) 是 c.LLVMIR(...) 的语法糖(裸名调用,无需 import c),两者完全等价。
设计期望无返回值(void),直接嵌入原始 LLVM IR 文本(不做指令解析);当前过渡期调用可带第二类型参数取得返回值(llvmir_test 用法),后续以无返回值为准并修订代码:
__llvmir__(f"{irout(r)} = add i32 {irinp(a)}, {irinp(b)}")
irinp / irout(c.LInp / c.LOut)
标记 LLVM IR 的输入/输出操作数,裸名写法与 c. 前缀写法等价:
irinp(expr) # 输入操作数:替换为 expr 的 LLVM 值引用文本(%x 或常量文本)
irout(expr) # 输出操作数:为 expr 变量分配唯一 SSA 寄存器 %llvmirN,
# 模板嵌入后自动追加 store 写回变量 alloca
旧写法 c.LLVMIR / c.LInp / c.LOut 全部保留可用。
运算符重载
对象类支持运算符重载,详见 06-oop.md 中 运算符重载。