diff --git a/course/basic/advanced_type/array.md b/course/basic/advanced_type/array.md index a11d4e25..104c0af9 100644 --- a/course/basic/advanced_type/array.md +++ b/course/basic/advanced_type/array.md @@ -24,6 +24,12 @@ outline: deep 关于[越界问题](https://ziglang.org/documentation/master/#Index-out-of-Bounds),zig 在编译期和运行时均有完整的越界保护和完善的堆栈错误跟踪。 +### 解构数组 + +我们在变量声明的章节提到了,数组可以结构,再来回忆一下: + +<<<@/code/release/array.zig#deconstruct + ### 多维数组 多维数组(矩阵)实际上就是嵌套数组,我们很容易就可以创建一个多维数组出来: diff --git a/course/basic/advanced_type/pointer.md b/course/basic/advanced_type/pointer.md index cea98dc8..5fdaf92a 100644 --- a/course/basic/advanced_type/pointer.md +++ b/course/basic/advanced_type/pointer.md @@ -10,7 +10,7 @@ outline: deep **取地址**:通过 `&` 符号来获取某个变量所对应的内存地址,如 `&integer` 就是获取变量 `integer` 的内存地址。 -与 C 不同,Zig 中的指针类型有多种,主要是对指向的元素做了区分,便于更好地使用。下图展示了它们指向元素的不同: +与 C 不同,Zig 中的指针类型要分为两种(一种是单项指针,一种是多项指针),它们主要是对指向的元素做了区分,便于更好地使用。下图展示了它们指向元素的不同: ![pointer representation](/picture/basic/pointer-representation.svg) @@ -36,23 +36,32 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 <<<@/code/release/pointer.zig#single_pointer +单项指针本身支持以下操作: + +- 解引用语法 `ptr.*` +- 切片语法 `ptr[0..1]` +- 指针减法 `ptr - ptr` + :::info 🅿️ 提示 -函数指针略有特殊:`const Call2Op = *const fn (a: i8, b: i8) i8;` +函数指针略有特殊: + +<<<@/code/release/pointer.zig#fn_pointer ::: ## 多项指针 -多项指针指向位置数量的多个元素。 +多项指针指向未知数量的多个元素。 多项指针的类型为:`[*]T`,`T`是所指向内存区域的类型,且该类型必须具有明确的大小(这意味着它不能是 [`anyopaque`](https://ziglang.org/documentation/master/#toc-C-Type-Primitives) 和其他任意[不透明类型](https://ziglang.org/documentation/master/#opaque))。 解引用方法支持以下几种: - 索引语法 `ptr[i]` -- 切片语法 `ptr[start..end]` -- 指针运算 `ptr + x`,`ptr - x` +- 切片语法 `ptr[start..end]` 和 `ptr[start..]` +- 指针运算 `ptr + int`, `ptr - int` +- 指针减法 `ptr - ptr` <<<@/code/release/pointer.zig#multi_pointer @@ -62,9 +71,21 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 `*[N]T`:这是指向一个数组的单项指针,数组的长度为 N。也可以将其理解为指向 N 个元素的指针。 +支持这些语法: + +- 索引语法:`array_ptr[i]` +- 切片语法:`array_ptr[start..end]` +- `len` 属性:`array_ptr.len` +- 指针减法:`array_ptr - array_ptr` + `[]T`:这是切片,相当于一个胖指针,包含了一个类型为 `[*]T` 的指针和一个长度。 -数组指针的类型中就包含了长度信息,而切片中则实际存储着长度。数组指针和切片的长度都可以通过 `len` 属性来获取。 +支持这些语法: + +- 索引语法:`slice[i]` +- 切片语法:`slice[start..end]` +- `len` 属性:`slice.len` + 数组指针的类型中就包含了长度信息,而切片中则实际存储着长度。数组指针和切片的长度都可以通过 `len` 属性来获取。 :::details 示例 @@ -122,6 +143,20 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 其中 `[*] const u8` 可以看作是 C 中的 `* const char`,这是因为在 C 语言中一个普通的指针也可以指向一个数组,zig 仅仅是单独把这种令人迷惑的行为单独作为一个语法而已! +## 指针和整数互转 + +[`@ptrFromInt`](https://ziglang.org/documentation/master/#ptrFromInt) 可以将整数地址转换为指针,[`@intFromPtr`](https://ziglang.org/documentation/master/#intFromPtr) 可以将指针转换为整数: + +<<<@/code/release/pointer.zig#ptr2int + +## 指针强制转换 + +内置函数 [`@ptrCast`](https://ziglang.org/documentation/master/#ptrCast) 可以将将指针的元素类型转换为另一种类型,也就是不同类型的指针强制转换。 + +一般情况下,应当尽量避免使用 `@ptrCast`,这会创建一个新的指针,根据通过它的加载和存储操作,可能导致无法检测的非法行为。 + +<<<@/code/release/pointer.zig#ptr_cast + ## 额外特性 以下的是指针的额外特性,初学者可以直接略过以下部分,等到你需要时再来学习即可! @@ -142,7 +177,7 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 > 如果你不知道内存对齐的含义是什么,那么本节内容你可以跳过了,等到你需要时再来查看! -每种类型都有一个对齐方式——数个字节,这样,当从内存加载或存储该类型的值时,内存地址必须能被该数字整除。我们可以使用 `@alignOf` 找出任何类型的内存对齐大小。 +每种类型都有一个对齐方式——也就是数个字节,这样,当从内存加载或存储该类型的值时,内存地址必须能被该数字整除。我们可以使用 `@alignOf` 找出任何类型的内存对齐大小。 内存对齐大小取决于 CPU 架构,但始终是 2 的幂,并且小于 1 << 29。 :::info @@ -168,6 +203,51 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 ::: +如果有一个指针或切片,它的对齐很小,但我们知道它实际上有一个更大的对齐,那么使用 [`@alignCast`](https://ziglang.org/documentation/master/#alignCast) 让其 `align` 更大。在运行时是无操作的,但会额外加入一个 [安全检查](https://ziglang.org/documentation/master/#Incorrect-Pointer-Alignment): + +> 例如这段代码就是错误的,不会被正常执行 + +```zig +const std = @import("std"); + +test "pointer alignment safety" { + var array align(4) = [_]u32{ 0x11111111, 0x11111111 }; + const bytes = std.mem.sliceAsBytes(array[0..]); + try std.testing.expect(foo(bytes) == 0x11111111); +} +fn foo(bytes: []u8) u32 { + const slice4 = bytes[1..5]; + const int_slice = std.mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4))); + return int_slice[0]; +} +``` + +```sh +$ zig test test_incorrect_pointer_alignment.zig +1/1 test_incorrect_pointer_alignment.test.pointer alignment safety...thread 958173 panic: incorrect alignment +/home/ci/actions-runner/_work/zig-bootstrap/zig/doc/langref/test_incorrect_pointer_alignment.zig:10:68: 0x1048962 in foo (test) + const int_slice = std.mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4))); + ^ +/home/ci/actions-runner/_work/zig-bootstrap/zig/doc/langref/test_incorrect_pointer_alignment.zig:6:31: 0x104880f in test.pointer alignment safety (test) + try std.testing.expect(foo(bytes) == 0x11111111); + ^ +/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:214:25: 0x10efab9 in mainTerminal (test) + if (test_fn.func()) |_| { + ^ +/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:62:28: 0x10e7ead in main (test) + return mainTerminal(); + ^ +/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:647:22: 0x10e7430 in posixCallMainAndExit (test) + root.main(); + ^ +/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:271:5: 0x10e6ffd in _start (test) + asm volatile (switch (native_arch) { + ^ +???:?:?: 0x0 in ??? (???) +error: the following test command crashed: +/home/ci/actions-runner/_work/zig-bootstrap/out/zig-local-cache/o/608e4a8451ecb0974638281c85927599/test --seed=0x9bc870fd +``` + ### 零指针 零指针实际上是一个未定义的错误行为([Pointer Cast Invalid Null](https://ziglang.org/documentation/master/#Pointer-Cast-Invalid-Null)),但是当我们给指针增加上 `allowzero` 修饰符后,它就变成合法的行为了! @@ -185,3 +265,7 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最 只要代码不依赖于未定义的内存布局,那么指针也可以在编译期发挥作用! <<<@/code/release/pointer.zig#comptime_pointer + +只要指针从未被取消引用,Zig 就能够保留 `comptime` 代码中的内存地址: + +<<<@/code/release/pointer.zig#comp_pointer diff --git a/course/basic/advanced_type/vector.md b/course/basic/advanced_type/vector.md index c3fe424f..2fefdc70 100644 --- a/course/basic/advanced_type/vector.md +++ b/course/basic/advanced_type/vector.md @@ -6,6 +6,8 @@ outline: deep > 向量(Vector)为我们提供了并行操纵一组同类型(布尔、整型、浮点、指针)的值的方法,它尽可能使用 `SIMD` 指令。 +向量类型使用内置函数 [@Vector](https://ziglang.org/documentation/master/#Vector) 创建 + ## 基本使用 向量支持与底层基本类型相同的内置运算符。这些操作是按元素执行,并返回与输入向量长度相同的向量,包括: @@ -30,6 +32,12 @@ Zig 支持任何已知的最大 2^32-1 向量长度。请注意,过长的向 ::: +## 解构向量 + +和数组一样,向量也可以被解构: + +<<<@/code/release/vector.zig#deconstruct + ## `@splat` `@splat(scalar: anytype) anytype` diff --git a/course/basic/define-variable.md b/course/basic/define-variable.md index 047a32d0..224a1211 100644 --- a/course/basic/define-variable.md +++ b/course/basic/define-variable.md @@ -69,6 +69,12 @@ zig 使用 `const` 作为关键字来声明常量,它无法再被更改,只 <<<@/code/release/define_variable.zig#deconstruct +解构表达式只能出现在块内(不在容器范围内),赋值的左侧必须由逗号分隔的列表组成,其中每个元素可以是左值(例如`var`)或变量声明: + +<<<@/code/release/define_variable.zig#deconstruct_2 + +解构可以以 `comptime` 关键字作为前缀,在这种情况下,整个解构表达式在 `comptime` 处求值。所有声明的 `var` 都将是 `comptime var`,并且所有表达式(左值和右值)都在 `comptime` 处求值。 + ## 块 块(block)用于限制变量声明的范围,例如以下代码是非法的: diff --git a/course/code/14/array.zig b/course/code/14/array.zig index d0d924e8..31b44246 100644 --- a/course/code/14/array.zig +++ b/course/code/14/array.zig @@ -21,6 +21,28 @@ const CreateArray = struct { // #endregion create_array }; +const Deconstruct = struct { + // #region deconstruct + const print = @import("std").debug.print; + + fn swizzleRgbaToBgra(rgba: [4]u8) [4]u8 { + // 解构 + const r, const g, const b, const a = rgba; + return .{ b, g, r, a }; + } + + pub fn main() void { + const pos = [_]i32{ 1, 2 }; + // 解构 + const x, const y = pos; + print("x = {}, y = {}\n", .{ x, y }); + + const orange: [4]u8 = .{ 255, 165, 0, 255 }; + print("{any}\n", .{swizzleRgbaToBgra(orange)}); + } + // #endregion deconstruct +}; + const Matrix = struct { // #region matrix const print = @import("std").debug.print; diff --git a/course/code/14/define_variable.zig b/course/code/14/define_variable.zig index 6258928c..8db019f5 100644 --- a/course/code/14/define_variable.zig +++ b/course/code/14/define_variable.zig @@ -137,13 +137,53 @@ const Block = struct { const Deconstruct = struct { fn main() void { // #region deconstruct + const print = @import("std").debug.print; + var x: u32 = undefined; + var y: u32 = undefined; var z: u32 = undefined; - // var z: u32 = undefined; - const x, var y, z = [3]u32{ 1, 2, 3 }; - y += 10; - // x 是 1,y 是 2,z 是 3 + // 元组 + const tuple = .{ 1, 2, 3 }; + // 解构元组 + x, y, z = tuple; + + print("tuple: x = {}, y = {}, z = {}\n", .{ x, y, z }); + // 数组 + const array = [_]u32{ 4, 5, 6 }; + // 解构数组 + x, y, z = array; + + print("array: x = {}, y = {}, z = {}\n", .{ x, y, z }); + // 向量定义 + const vector: @Vector(3, u32) = .{ 7, 8, 9 }; + // 解构向量 + x, y, z = vector; + + print("vector: x = {}, y = {}, z = {}\n", .{ x, y, z }); // #endregion deconstruct - _ = x; + + } +}; + +const Deconstruct_2 = struct { + pub fn main() !void { + // #region deconstruct_2 + const print = @import("std").debug.print; + var x: u32 = undefined; + + const tuple = .{ 1, 2, 3 }; + + x, var y: u32, const z = tuple; + + print("x = {}, y = {}, z = {}\n", .{ x, y, z }); + + // y 可变 + y = 100; + + // 可以用 _ 丢弃不想要的值 + _, x, _ = tuple; + + print("x = {}", .{x}); + // #endregion deconstruct_2 } }; diff --git a/course/code/14/pointer.zig b/course/code/14/pointer.zig index de51acd7..6af33843 100644 --- a/course/code/14/pointer.zig +++ b/course/code/14/pointer.zig @@ -9,6 +9,8 @@ pub fn main() !void { try AlignCast.main(); try ZeroPointer.main(); ComptimePointer.main(); + ptr2int.main(); + try compPointer.main(); } const SinglePointer = struct { @@ -24,6 +26,34 @@ const SinglePointer = struct { } // #endregion single_pointer }; + +const fnPointer = struct { + // #region fn_pointer + const Call2Op = *const fn (a: i8, b: i8) i8; + // Call20p 是一个函数指针类型,指向一个接受两个 i8 类型参数并返回 i8 类型的函数 + // #endregion fn_pointer +}; + +const ptr2int = struct { + pub fn main() void { + // #region ptr2int + const std = @import("std"); + + // ptrFromInt 将整数转换为指针 + const ptr: *i32 = @ptrFromInt(0xdeadbee0); + // intFromPtr 将指针转换为整数 + const addr = @intFromPtr(ptr); + + if (@TypeOf(addr) == usize) { + std.debug.print("success\n", .{}); + } + if (addr == 0xdeadbee0) { + std.debug.print("success\n", .{}); + } + // #endregion ptr2int + } +}; + const MultiPointer = struct { // #region multi_pointer const print = @import("std").debug.print; @@ -192,3 +222,45 @@ const ComptimePointer = struct { } // #endregion comptime_pointer }; + +const compPointer = struct { + pub fn main() !void { + // #region comp_pointer + comptime { + const expect = @import("std").testing.expect; + // 只要指针不被解引用,那么就可以这么做 + const ptr: *i32 = @ptrFromInt(0xdeadbee0); + const addr = @intFromPtr(ptr); + try expect(@TypeOf(addr) == usize); + try expect(addr == 0xdeadbee0); + } + // #endregion comp_pointer + } +}; + +const ptrCast = struct { + const std = @import("std"); + pub fn main() !void { + // #region ptr_cast + const bytes align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12 }; + // 将 u8数组指针 转换为 u32 类型的指针 + const u32_ptr: *const u32 = @ptrCast(&bytes); + + if (u32_ptr.* == 0x12121212) { + std.debug.print("success\n", .{}); + } + + // 通过标准库转为 u32 + const u32_value = std.mem.bytesAsSlice(u32, bytes[0..])[0]; + + if (u32_value == 0x12121212) { + std.debug.print("success\n", .{}); + } + + // 通过内置函数转换 + if (@as(u32, @bitCast(bytes)) == 0x12121212) { + std.debug.print("success\n", .{}); + } + // #endregion ptr_cast + } +}; diff --git a/course/code/14/vector.zig b/course/code/14/vector.zig index acfa886b..993ab895 100644 --- a/course/code/14/vector.zig +++ b/course/code/14/vector.zig @@ -49,6 +49,24 @@ const Splat = struct { } }; +const Deconstruct = struct { + // #region deconstruct + const print = @import("std").debug.print; + + pub fn unpack(x: @Vector(4, f32), y: @Vector(4, f32)) @Vector(4, f32) { + const a, const c, _, _ = x; + const b, const d, _, _ = y; + return .{ a, b, c, d }; + } + + pub fn main() void { + const x: @Vector(4, f32) = .{ 1.0, 2.0, 3.0, 4.0 }; + const y: @Vector(4, f32) = .{ 5.0, 6.0, 7.0, 8.0 }; + print("{}", .{unpack(x, y)}); + } + // #endregion deconstruct +}; + const Reduce = struct { const std = @import("std"); const print = std.debug.print; diff --git a/course/index.md b/course/index.md index 34812941..5fbdb6f6 100644 --- a/course/index.md +++ b/course/index.md @@ -20,6 +20,8 @@ showVersion: false > 允许函数处理各种数据,以及一小组新的编译器指令,以允许使用反射访问有关这些类型的信息。 > Zig 还旨在提高代码的安全性,它不提供垃圾回收(GC),但是使用可选类型代替 `null` ,这避免了空指针的出现。 +![Cover Image](./public/cover_image.png "Cover Image") + ## 为何使用 Zig 从本质上看,Zig 是一门 `low level` 的高级语言,它和 C 很像,但改善旧问题并提供了完善的工具链,并且它可选支持 `libc`。 diff --git a/course/prologue.md b/course/prologue.md index db5d996c..49c61566 100644 --- a/course/prologue.md +++ b/course/prologue.md @@ -27,3 +27,5 @@ C 很好,非常好,它非常成功,以至于 C 现在已经不再是一门 或许可能有人会跟我说 Rust 比 Zig 好,我要说的是你说的基本是对的,目前情况来看,Rust 的的确确比 Zig 好很多,更为完善的生态,更多能拿得出手的代表项目,以及相较 Zig 庞大很多的社区等等,但是在未来谁说的准呢?更何况 Rust 和 Zig 并不是一个赛道上的东西,在我看来,Rust 的目标是 C++ 的替代,因此我更愿意称之为“披着高抽象皮的 low level 语言”,Zig 的目标则是 C,而且目前 Zig 的特性也的确在这个方向发展。 Zig 的社区需要更多的人来构建,所以我写了这个文档,帮助新人来更好的理解和学习 Zig! + +![Cover Image](./public/cover_image.png "Cover Image") diff --git a/course/public/cover_image.png b/course/public/cover_image.png new file mode 100644 index 00000000..36297906 Binary files /dev/null and b/course/public/cover_image.png differ