接触rust的时候,无意中认识了zig,目前版本是zig 0.10.0,还没有正式的1.0版本。
初步使用的感受:
1). 用zig写出的代码更防崩,不会像C那样出现很多内存非法访问的情况
(比如这些情形:栈保护、整数溢出、下标越界、OOM、DF、Leak。。。)
2). 拥有“类”语法,相同功能的项目开发过程中,所需编写的总代码量肯定会比C少
3). 比Rust/C++容易学,但是比C稍难学一点点
4). 基于LLVM开发的编译器,天生支持交叉编译多种cpu架构的目标代码。
5). 可与C、asm进行混合开发,兼容C/C++,完全可以拿来当另一个C编译器用
6). 支持编译期代码
7). 可以源码中集成单元测试代码
8). 自带基于自身脚本的构建系统,不需要makefile之类的
9). 支持 try catch exception处理
10). 编译速度快(网传)
11). MIT许可(相当宽松)
12). 原生支持异步
13). 支持将C代码转换成zig代码
缺点及风险:
1). 小团队开发维护的编程语言,目前网上能看到的只有跟Uber一家有合作。
2). 未成熟、定型,还在变,比如三年前的zig项目,现在已经不能直接编译了。(构建脚本已变)
3). 小众,国内用的人更加少,文档不全,中文的资料更是少得可怜。
安装过程很简单
官网有编译好的,下载解压,添加路径即可。
下载 ⚡Zig Programming Language
只有一个可执行文件:zig
项目创建、构建都是用这个指令。
下面是用zig做aarch64(armv8a)裸机开发的内容,展示了zig的基本用法:
包括基本语法、类的静态成员函数、类实例的成员函数、
与C/asm互调、混合编译、链接、构建脚本、交叉编译、MMIO读写。。。
源码目录结构:
编译指令:
zig build #生成elf格式文件,体积为130Kb上下
zig build bin #生成bin格式文件,40Kb,不调用C的话27Kb左右
在build.zig中将编译模式调整为 "safety off" 后,编译体积甚至比C还要小,bin文件输出不到1kb!
elf.setBuildMode(std.builtin.Mode.ReleaseSmall);//mode);
src/main.zig:
const std = @import("std");
const io = std.io;
const os = std.os;
const a_number: i32 = 1234;
export fn main() void {
const uart = UART.init();
const out = uart.getWriter();
out.print("Hello1, {s}!\r\n", .{"world"}) catch return;
UART.putc(0x63);
UART.putc('a');
UART.putc('\n');
out.print("Hello2, {s} {}==0x{X}!\r\n", .{"世界", a_number, a_number}) catch return;
out.print("Hello3, {}\r\n", .{cEng.c_func(3, 8)}) catch return;
while(true)
{
var c = UART.getc();
out.print("-->{c}!\r\n", .{c}) catch return;
}
out.print("Hello4, {s}!\r\n", .{"world"}) catch return;
}
const UART0DR = @intToPtr(*volatile u8, 0x09000000);
const UART0FR = @intToPtr(*volatile u8, 0x09000018);
pub const UART = struct {
inited: i32 = 0,
pub fn init() UART {
return UART {
.inited = 1,
};
}
pub fn putc(ch:u8) void
{
while ( ((UART0FR.*) & (1 << 5)) != 0 ) {}
UART0DR.* = ch;
}
pub fn getc() u8
{
while ( ((UART0FR.*) & (1 << 4)) != 0 ) {}
var ch = UART0DR.*;
return ch;
}
pub fn doWrite(self:UART, bytes: []const u8) WriteError!usize {
if(self.inited > 0) {}
var i:usize=0;
for (bytes) |c| {
putc(c);
i += 1;
}
return i;
}
pub const WriteError = os.WriteError;
pub const Writer = io.Writer(UART, WriteError, doWrite);
pub fn getWriter(uart: UART) Writer {
return .{ .context = uart };
}
};
export fn zig_func(a: i32, b: i32) i32 {
return a + b;
}
const cEng = @cImport({
@cDefine("ZIG_WITH_C", "1");
@cInclude("coWork.h");
});
src/boot.S:
.section ".text.boot"
.global _start
_start:
mrs x1, mpidr_el1
and x1, x1, #3
cbz x1, 2f
1:
wfe
b 1b
2:
ldr x0, =stack_top
bic sp, x0, #0xf /* 16-byte alignment for ABI compliance */
bl main
bl .
src/linker.ld:
#include <Platform_def.h>
/*
OUTPUT_FORMAT("elf64-littleaarch64", "elf64-littleaarch64", "elf64-littleaarch64")
OUTPUT_ARCH(aarch64)
*/
ENTRY(_start)
SECTIONS {
. = 0x40000000;
. = ALIGN(8);
__text_start = .;
.text :
{
KEEP(*(.text.boot))
STARTOBJ(.text)
*(.text)
}
/* shell_cmd_item 保存在这个段 */
. = ALIGN(8);
__shell_cmd_list_start = .;
.ShellCmdList ALIGN(8) : { *(.ShellCmdList) }
__shell_cmd_list_end = .;
/* resource_item 保存在这个段 */
. = ALIGN(8);
__resource_item_start = .;
.resource_item ALIGN(8) : { *(.ResourceList) }
__resource_item_end = .;
.rodata ALIGN(8) : {*(.rodata*)}
.data ALIGN(8) : { *(.data) }
. = ALIGN(8);
__bss_start = .;
.bss ALIGN(8) : { *(.bss) *(COMMON) }
__bss_end = .;
/* resource 实际保存在这个段 */
. = ALIGN(8);
.extdata ALIGN(8): {*(.extdata*)}
/* 栈内存 */
. = ALIGN(16);
. = . + 1M; /* 4kB of stack memory => 1MB */
stack_top = .;
/DISCARD/ : { *(.dynsym) }
/DISCARD/ : { *(.dynstr*) }
/DISCARD/ : { *(.dynamic*) }
/DISCARD/ : { *(.plt*) }
/DISCARD/ : { *(.interp*) }
/DISCARD/ : { *(.gnu*) }
}
#ifdef TPL
/* tpl 运行在64kb 的iRam 中,体积不能超了,况且iRam分出来4kb做栈内存了,实际可用的内存就更少了,所以定个58kb的可用内存 */
ASSERT( (__bss_start - __text_start) < (58*1024),
"
编译出来的 tpl.bin 太大,iRam 装不下了,请编译为 spl => make spl
");
#endif
src/coWork.h:
int c_func(int a, int b);
src/coWork.c:
extern int zig_func(int a, int b);
int c_func(int a, int b) {
return zig_func(a, a) + b;
}
startQemu.ps1:
[System.Console]::OutputEncoding = [System.Console]::InputEncoding = [System.Text.Encoding]::UTF8
& "d:\Program Files\qemu\qemu-system-aarch64.exe" -nographic -machine virt-6.2,gic-version=3,secure=on,virtualization=on -cpu cortex-a53 -m 1024 -semihosting -kernel ".\zig-out\bin\out.elf"
build.zig:
const Builder = @import("std").build.Builder;
const builtin = @import("builtin");
const std = @import("std");
pub fn build(b: *Builder) void {
const target = .{
.cpu_arch = .aarch64,
.cpu_model = .{ .explicit = &std.Target.aarch64.cpu.cortex_a53 },
.os_tag = .freestanding,
.abi = .none,
};
// Standard release options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall.
const mode = b.standardReleaseOptions();
const elf = b.addExecutable("out.elf", "src/main.zig");
elf.addAssemblyFile("src/boot.S");
elf.addIncludePath("src/"); // addIncludeDir 已经被官方标为deprecated,不建议使用
elf.addCSourceFile("src/coWork.c", &[_][]const u8{"-std=c99"});
elf.setTarget(target);
elf.setBuildMode(mode);
elf.setLinkerScriptPath(.{ .path = "src/linker.ld" });
// --script
const bin = b.addInstallRaw(elf, "out.bin", .{});
const bin_step = b.step("bin", "Generate binary file to be flashed");
bin_step.dependOn(&bin.step);
b.default_step.dependOn(&elf.step);
b.installArtifact(elf);
}
qemu运行截图:
通过 struct {} 定义类时,大括号里面的成员函数,
如果它的第一个参数的类型是类本身,
则此函数被当成类的实例的成员函数(比如代码中的 uart.getWriter ),
否则被当成类的静态函数,要通过 “类名.函数名的方式调用”,(比如代码中的 UART.putc )
zig 支持的目标cpu架构,可参考此目录:$(zig_install_dir)/lib/std/target/
也可能通过运行这条指令列出:zig targets
参考:
GitHub - rbino/zig-stm32-blink: Use Zig to blink some LEDs
Documentation - The Zig Programming Language
Documentation - Zig
0.10.0 Release Notes ⚡ The Zig Programming Language