Skip to content

Commit a9ce5a0

Browse files
committed
update: 补充指针章节内容
1 parent 2e574b2 commit a9ce5a0

File tree

2 files changed

+163
-7
lines changed

2 files changed

+163
-7
lines changed

course/basic/advanced_type/pointer.md

Lines changed: 91 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ outline: deep
1010

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

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

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

@@ -36,23 +36,32 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最
3636

3737
<<<@/code/release/pointer.zig#single_pointer
3838

39+
单项指针本身支持以下操作:
40+
41+
- 解引用语法 `ptr.*`
42+
- 切片语法 `ptr[0..1]`
43+
- 指针减法 `ptr - ptr`
44+
3945
:::info 🅿️ 提示
4046

41-
函数指针略有特殊:`const Call2Op = *const fn (a: i8, b: i8) i8;`
47+
函数指针略有特殊:
48+
49+
<<<@/code/release/pointer.zig#fn_pointer
4250

4351
:::
4452

4553
## 多项指针
4654

47-
多项指针指向位置数量的多个元素
55+
多项指针指向未知数量的多个元素
4856

4957
多项指针的类型为:`[*]T``T`是所指向内存区域的类型,且该类型必须具有明确的大小(这意味着它不能是 [`anyopaque`](https://ziglang.org/documentation/master/#toc-C-Type-Primitives) 和其他任意[不透明类型](https://ziglang.org/documentation/master/#opaque))。
5058

5159
解引用方法支持以下几种:
5260

5361
- 索引语法 `ptr[i]`
54-
- 切片语法 `ptr[start..end]`
55-
- 指针运算 `ptr + x``ptr - x`
62+
- 切片语法 `ptr[start..end]``ptr[start..]`
63+
- 指针运算 `ptr + int`, `ptr - int`
64+
- 指针减法 `ptr - ptr`
5665

5766
<<<@/code/release/pointer.zig#multi_pointer
5867

@@ -62,9 +71,21 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最
6271

6372
`*[N]T`:这是指向一个数组的单项指针,数组的长度为 N。也可以将其理解为指向 N 个元素的指针。
6473

74+
支持这些语法:
75+
76+
- 索引语法:`array_ptr[i]`
77+
- 切片语法:`array_ptr[start..end]`
78+
- `len` 属性:`array_ptr.len`
79+
- 指针减法:`array_ptr - array_ptr`
80+
6581
`[]T`:这是切片,相当于一个胖指针,包含了一个类型为 `[*]T` 的指针和一个长度。
6682

67-
数组指针的类型中就包含了长度信息,而切片中则实际存储着长度。数组指针和切片的长度都可以通过 `len` 属性来获取。
83+
支持这些语法:
84+
85+
- 索引语法:`slice[i]`
86+
- 切片语法:`slice[start..end]`
87+
- `len` 属性:`slice.len`
88+
数组指针的类型中就包含了长度信息,而切片中则实际存储着长度。数组指针和切片的长度都可以通过 `len` 属性来获取。
6889

6990
:::details 示例
7091

@@ -122,6 +143,20 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最
122143

123144
其中 `[*] const u8` 可以看作是 C 中的 `* const char`,这是因为在 C 语言中一个普通的指针也可以指向一个数组,zig 仅仅是单独把这种令人迷惑的行为单独作为一个语法而已!
124145

146+
## 指针和整数互转
147+
148+
[`@ptrFromInt`](https://ziglang.org/documentation/master/#ptrFromInt) 可以将整数地址转换为指针,[`@intFromPtr`](https://ziglang.org/documentation/master/#intFromPtr) 可以将指针转换为整数:
149+
150+
<<<@/code/release/pointer.zig#ptr2int
151+
152+
## 指针强制转换
153+
154+
内置函数 [`@ptrCast`](https://ziglang.org/documentation/master/#ptrCast) 可以将将指针的元素类型转换为另一种类型,也就是不同类型的指针强制转换。
155+
156+
一般情况下,应当尽量避免使用 `@ptrCast`,这会创建一个新的指针,根据通过它的加载和存储操作,可能导致无法检测的非法行为。
157+
158+
<<<@/code/release/pointer.zig#ptr_cast
159+
125160
## 额外特性
126161

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

143178
> 如果你不知道内存对齐的含义是什么,那么本节内容你可以跳过了,等到你需要时再来查看!
144179
145-
每种类型都有一个对齐方式——数个字节,这样,当从内存加载或存储该类型的值时,内存地址必须能被该数字整除。我们可以使用 `@alignOf` 找出任何类型的内存对齐大小。
180+
每种类型都有一个对齐方式——也就是数个字节,这样,当从内存加载或存储该类型的值时,内存地址必须能被该数字整除。我们可以使用 `@alignOf` 找出任何类型的内存对齐大小。
146181

147182
内存对齐大小取决于 CPU 架构,但始终是 2 的幂,并且小于 1 << 29。
148183
:::info
@@ -168,6 +203,51 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最
168203

169204
:::
170205

206+
如果有一个指针或切片,它的对齐很小,但我们知道它实际上有一个更大的对齐,那么使用 [`@alignCast`](https://ziglang.org/documentation/master/#alignCast) 让其 `align` 更大。在运行时是无操作的,但会额外加入一个 [安全检查](https://ziglang.org/documentation/master/#Incorrect-Pointer-Alignment)
207+
208+
> 例如这段代码就是错误的,不会被正常执行
209+
210+
```zig
211+
const std = @import("std");
212+
213+
test "pointer alignment safety" {
214+
var array align(4) = [_]u32{ 0x11111111, 0x11111111 };
215+
const bytes = std.mem.sliceAsBytes(array[0..]);
216+
try std.testing.expect(foo(bytes) == 0x11111111);
217+
}
218+
fn foo(bytes: []u8) u32 {
219+
const slice4 = bytes[1..5];
220+
const int_slice = std.mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
221+
return int_slice[0];
222+
}
223+
```
224+
225+
```sh
226+
$ zig test test_incorrect_pointer_alignment.zig
227+
1/1 test_incorrect_pointer_alignment.test.pointer alignment safety...thread 958173 panic: incorrect alignment
228+
/home/ci/actions-runner/_work/zig-bootstrap/zig/doc/langref/test_incorrect_pointer_alignment.zig:10:68: 0x1048962 in foo (test)
229+
const int_slice = std.mem.bytesAsSlice(u32, @as([]align(4) u8, @alignCast(slice4)));
230+
^
231+
/home/ci/actions-runner/_work/zig-bootstrap/zig/doc/langref/test_incorrect_pointer_alignment.zig:6:31: 0x104880f in test.pointer alignment safety (test)
232+
try std.testing.expect(foo(bytes) == 0x11111111);
233+
^
234+
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:214:25: 0x10efab9 in mainTerminal (test)
235+
if (test_fn.func()) |_| {
236+
^
237+
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/compiler/test_runner.zig:62:28: 0x10e7ead in main (test)
238+
return mainTerminal();
239+
^
240+
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:647:22: 0x10e7430 in posixCallMainAndExit (test)
241+
root.main();
242+
^
243+
/home/ci/actions-runner/_work/zig-bootstrap/out/host/lib/zig/std/start.zig:271:5: 0x10e6ffd in _start (test)
244+
asm volatile (switch (native_arch) {
245+
^
246+
???:?:?: 0x0 in ??? (???)
247+
error: the following test command crashed:
248+
/home/ci/actions-runner/_work/zig-bootstrap/out/zig-local-cache/o/608e4a8451ecb0974638281c85927599/test --seed=0x9bc870fd
249+
```
250+
171251
### 零指针
172252
173253
零指针实际上是一个未定义的错误行为([Pointer Cast Invalid Null](https://ziglang.org/documentation/master/#Pointer-Cast-Invalid-Null)),但是当我们给指针增加上 `allowzero` 修饰符后,它就变成合法的行为了!
@@ -185,3 +265,7 @@ zig 本身支持指针运算(加减操作),但有一点需要注意:最
185265
只要代码不依赖于未定义的内存布局,那么指针也可以在编译期发挥作用!
186266
187267
<<<@/code/release/pointer.zig#comptime_pointer
268+
269+
只要指针从未被取消引用,Zig 就能够保留 `comptime` 代码中的内存地址:
270+
271+
<<<@/code/release/pointer.zig#comp_pointer

course/code/14/pointer.zig

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub fn main() !void {
99
try AlignCast.main();
1010
try ZeroPointer.main();
1111
ComptimePointer.main();
12+
ptr2int.main();
13+
try compPointer.main();
1214
}
1315

1416
const SinglePointer = struct {
@@ -24,6 +26,34 @@ const SinglePointer = struct {
2426
}
2527
// #endregion single_pointer
2628
};
29+
30+
const fnPointer = struct {
31+
// #region fn_pointer
32+
const Call2Op = *const fn (a: i8, b: i8) i8;
33+
// Call20p 是一个函数指针类型,指向一个接受两个 i8 类型参数并返回 i8 类型的函数
34+
// #endregion fn_pointer
35+
};
36+
37+
const ptr2int = struct {
38+
pub fn main() void {
39+
// #region ptr2int
40+
const std = @import("std");
41+
42+
// ptrFromInt 将整数转换为指针
43+
const ptr: *i32 = @ptrFromInt(0xdeadbee0);
44+
// intFromPtr 将指针转换为整数
45+
const addr = @intFromPtr(ptr);
46+
47+
if (@TypeOf(addr) == usize) {
48+
std.debug.print("success\n", .{});
49+
}
50+
if (addr == 0xdeadbee0) {
51+
std.debug.print("success\n", .{});
52+
}
53+
// #endregion ptr2int
54+
}
55+
};
56+
2757
const MultiPointer = struct {
2858
// #region multi_pointer
2959
const print = @import("std").debug.print;
@@ -192,3 +222,45 @@ const ComptimePointer = struct {
192222
}
193223
// #endregion comptime_pointer
194224
};
225+
226+
const compPointer = struct {
227+
pub fn main() !void {
228+
// #region comp_pointer
229+
comptime {
230+
const expect = @import("std").testing.expect;
231+
// 只要指针不被解引用,那么就可以这么做
232+
const ptr: *i32 = @ptrFromInt(0xdeadbee0);
233+
const addr = @intFromPtr(ptr);
234+
try expect(@TypeOf(addr) == usize);
235+
try expect(addr == 0xdeadbee0);
236+
}
237+
// #endregion comp_pointer
238+
}
239+
};
240+
241+
const ptrCast = struct {
242+
const std = @import("std");
243+
pub fn main() !void {
244+
// #region ptr_cast
245+
const bytes align(@alignOf(u32)) = [_]u8{ 0x12, 0x12, 0x12, 0x12 };
246+
// 将 u8数组指针 转换为 u32 类型的指针
247+
const u32_ptr: *const u32 = @ptrCast(&bytes);
248+
249+
if (u32_ptr.* == 0x12121212) {
250+
std.debug.print("success\n", .{});
251+
}
252+
253+
// 通过标准库转为 u32
254+
const u32_value = std.mem.bytesAsSlice(u32, bytes[0..])[0];
255+
256+
if (u32_value == 0x12121212) {
257+
std.debug.print("success\n", .{});
258+
}
259+
260+
// 通过内置函数转换
261+
if (@as(u32, @bitCast(bytes)) == 0x12121212) {
262+
std.debug.print("success\n", .{});
263+
}
264+
// #endregion ptr_cast
265+
}
266+
};

0 commit comments

Comments
 (0)