Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions course/basic/advanced_type/array.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ outline: deep

关于[越界问题](https://ziglang.org/documentation/master/#Index-out-of-Bounds),zig 在编译期和运行时均有完整的越界保护和完善的堆栈错误跟踪。

### 解构数组

我们在变量声明的章节提到了,数组可以结构,再来回忆一下:

<<<@/code/release/array.zig#deconstruct

### 多维数组

多维数组(矩阵)实际上就是嵌套数组,我们很容易就可以创建一个多维数组出来:
Expand Down
98 changes: 91 additions & 7 deletions course/basic/advanced_type/pointer.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ outline: deep

**取地址**:通过 `&` 符号来获取某个变量所对应的内存地址,如 `&integer` 就是获取变量 `integer` 的内存地址。

与 C 不同,Zig 中的指针类型有多种,主要是对指向的元素做了区分,便于更好地使用。下图展示了它们指向元素的不同:
与 C 不同,Zig 中的指针类型要分为两种(一种是单项指针,一种是多项指针),它们主要是对指向的元素做了区分,便于更好地使用。下图展示了它们指向元素的不同:

![pointer representation](/picture/basic/pointer-representation.svg)

Expand All @@ -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

Expand All @@ -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 示例

Expand Down Expand Up @@ -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

## 额外特性

以下的是指针的额外特性,初学者可以直接略过以下部分,等到你需要时再来学习即可!
Expand All @@ -142,7 +177,7 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最

> 如果你不知道内存对齐的含义是什么,那么本节内容你可以跳过了,等到你需要时再来查看!

每种类型都有一个对齐方式——数个字节,这样,当从内存加载或存储该类型的值时,内存地址必须能被该数字整除。我们可以使用 `@alignOf` 找出任何类型的内存对齐大小。
每种类型都有一个对齐方式——也就是数个字节,这样,当从内存加载或存储该类型的值时,内存地址必须能被该数字整除。我们可以使用 `@alignOf` 找出任何类型的内存对齐大小。

内存对齐大小取决于 CPU 架构,但始终是 2 的幂,并且小于 1 << 29。
:::info
Expand All @@ -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` 修饰符后,它就变成合法的行为了!
Expand All @@ -185,3 +265,7 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最
只要代码不依赖于未定义的内存布局,那么指针也可以在编译期发挥作用!

<<<@/code/release/pointer.zig#comptime_pointer

只要指针从未被取消引用,Zig 就能够保留 `comptime` 代码中的内存地址:

<<<@/code/release/pointer.zig#comp_pointer
8 changes: 8 additions & 0 deletions course/basic/advanced_type/vector.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ outline: deep

> 向量(Vector)为我们提供了并行操纵一组同类型(布尔、整型、浮点、指针)的值的方法,它尽可能使用 `SIMD` 指令。

向量类型使用内置函数 [@Vector](https://ziglang.org/documentation/master/#Vector) 创建

## 基本使用

向量支持与底层基本类型相同的内置运算符。这些操作是按元素执行,并返回与输入向量长度相同的向量,包括:
Expand All @@ -30,6 +32,12 @@ Zig 支持任何已知的最大 2^32-1 向量长度。请注意,过长的向

:::

## 解构向量

和数组一样,向量也可以被解构:

<<<@/code/release/vector.zig#deconstruct

## `@splat`

`@splat(scalar: anytype) anytype`
Expand Down
6 changes: 6 additions & 0 deletions course/basic/define-variable.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)用于限制变量声明的范围,例如以下代码是非法的:
Expand Down
22 changes: 22 additions & 0 deletions course/code/14/array.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
50 changes: 45 additions & 5 deletions course/code/14/define_variable.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
};

Expand Down
72 changes: 72 additions & 0 deletions course/code/14/pointer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ pub fn main() !void {
try AlignCast.main();
try ZeroPointer.main();
ComptimePointer.main();
ptr2int.main();
try compPointer.main();
}

const SinglePointer = struct {
Expand All @@ -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;
Expand Down Expand Up @@ -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
}
};
Loading