

Zig语言如何解决C语言中的一些问题
source link: https://discretetom.github.io/posts/problems-of-c-and-how-zig-address-them/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

原文:Problems of C, and how Zig addresses them。本文结合自己的理解,做了很多魔改
使用 comptime 取代宏
C 语言的宏的经典问题:
#define SQUARE(x) x * x
int result = SQUARE(2 + 3)
Zig 的解决方案:使用关键字comptime
,可以在编译期执行代码(比如跑一个斐波那契数列什么的)
fn square(x: anytype) @TypeOf(x) {
return x * x;
}
const result = comptime square(2 + 3); // result = 25, at compile-time
使用 Allocator 实现内存管理
Zig 语言不像 Rust 语言一样基于生命周期管理内存,而是类似于 C。但是 Zig 语言提供了一个Allocator
的概念。一切在堆上申请内存的操作都需要提供 allocator,避免隐式分配内存
此外,std.testing.allocator
还可以检测内存泄漏,可以在测试里面使用
const std = @import("std");
test "detect leak" {
var list = std.ArrayList(u21).init(std.testing.allocator);
// defer list.deinit(); <- this line is missing
try list.append('☔');
try std.testing.expect(list.items.len == 1);
}
Option
类似 Rust 中的 Option,只不过使用?
代替
const Person = struct {
age: u8
};
const maybe_p: Person = null; // compile error: expected type 'Person', found '@Type(.Null)'
const maybe_p: ?Person = null; // OK
Slice 替代指针运算
在 C 中使用指针运算,容易访问到非法内存,而且不容易发现。Zig 中使用 Slice,可以避免这个问题
var arr = [_]u32{ 1, 2, 3, 4, 5, 6 }; // 1, 2, 3, 4, 5, 6
const slice1 = arr[1..5]; // 2, 3, 4, 5
const slice2 = slice1[1..3]; // 3, 4
检测内存布局中的错误
参考如下 C 代码,因为 char 类型的指针不是 4 字节对齐的,而 int 是 4 字节对齐的,所以会导致访问非法内存
int main() {
unsigned int* ptr;
char* misaligned_ptr;
char buffer[10];
// Intentionally misalign the pointer so it won't be evenly divisible by 4
misaligned_ptr = buffer + 3;
ptr = (unsigned int*)misaligned_ptr;
unsigned int value = *ptr;
printf("Value: %u\n", value);
return 0;
}
如果是 zig
pub fn main() void {
var buffer = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Intentionally misalign the pointer so it won't be evenly divisible by 4
var misaligned_ptr = &buffer[3];
var ptr: *u32 = @ptrCast(*u32, misaligned_ptr);
const value: u32 = ptr.*;
std.debug.print("Value: {}\n", .{value});
}
会报错:试图把 u8 指针转为 u32 指针,可能存在内存对齐的问题
.\main.zig:61:21: error: cast increases pointer alignment
var ptr: *u32 = @ptrCast(*u32, misaligned_ptr);
^
.\main.zig:61:36: note: '*u8' has alignment 1
var ptr: *u32 = @ptrCast(*u32, misaligned_ptr);
^
.\main.zig:61:30: note: '*u32' has alignment 4
var ptr: *u32 = @ptrCast(*u32, misaligned_ptr);
^
可以通过@alignCast
解决,在 safe build 模式下,会检查内存对齐的问题,并抛出异常和堆栈信息,以便排查问题
pub fn main() void {
var buffer = [_]u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
// Intentionally misalign the pointer so it won't be evenly divisible by 4
var misaligned_ptr = &buffer[3];
var ptr: *u32 = @ptrCast(*u32, @alignCast(4, misaligned_ptr));
const value: u32 = ptr.*;
std.debug.print("Value: {}\n", .{value});
}
// Compiles OK
按值传递数组
C 里面数组是指针,所以作为参数传递时,相当于传引用。而 Zig 里面数组是值,所以作为参数传递时,相当于传值
可以通过!
把错误类型和正常值的类型连接在一起
const FileOpenError = error{
AccessDenied,
OutOfMemory,
FileNotFound,
};
const maybe_error: FileOpenError!u16 = 10;
const no_error = maybe_error catch 0;
所以如果一个函数可能返回 error,实际上是在返回一个error!T
类型的值(union),然后通过try/catch
进行解析。
需要注意:这里的 try/catch 和其他编程语言的不一样,是对一个值进行的操作,而不是语句块
块可以作为表达式
类似 Rust:
let x = {
let y = 1;
y + 1
};
const firstName: ?*const [3:0]u8 = "Tom";
const lastName: ?*const [3:0]u8 = null;
var buf: [16]u8 = undefined;
const displayName = blk: {
if (firstName != null and lastName != null) {
const string = std.fmt.bufPrint(&buf, "{s} {s}", .{ firstName, lastName }) catch unreachable;
break :blk string;
}
if (firstName != null) break :blk firstName;
if (lastName != null) break :blk lastName;
break :blk "(no name)";
};
块都可以通过标签来获取值
愚蠢的前置类型声明
C 的老问题了,略
Recommend
-
48
Go语言TCP/IP网络编程 乍一看,通过TCP/IP层连接两个进程会感觉可怕, 但是在Go语言中可能比你想象的要简单的多。 TCP/IP层发送数据的应用场景 当然很多情况下,不是大多数情况下,使用更高级别的网络协议毫无疑...
-
59
前言 input是我们接受来自用户的数据常用标签,在前端开发中,相信每个人都会用到这个标签,所以在开发过程中也时候也会遇到一些问题,本文的内容是我在跟input相爱相杀过程中产生的,在此记录分享一下。如果喜欢的话可以点波赞/关注,支持一下,希望大家看完本文...
-
57
defer语句是Go中一个非常有用的特性,可以将一个方法延迟到包裹该方法的方法返回时执行,在实际应用中,defer语句可以充当其他语言中try…catch…的角色,也可以用来处理关闭文件句柄等收尾操作。 defer触发时机 A "d...
-
30
golang中的slice很灵活,功能也很强悍,不过对于初学者来说会容易被它坑到,此篇文章就尽量提及到使用slice的一些容易容易出错的地方,以下示例使用的golang版本为1.14.2。 作为参数传递 在go语言中的方法的参数都为...
-
3
Go 语言中的一些非常规优化 Xargin · 大约12小时之前 · 127 次点击 · 预计阅读时间 5 分钟 · 大约8小时之前 开始浏览
-
5
一些单片机比赛,开发中的问题解决办法
-
6
问题1 @Autowired 注入失败@Autowired private DingService dingService;在项目中service是用 @Autowired依赖注入,用debug测试的时候看到service确实为nul...
-
3
使用单调栈来解决的一些问题 作者:Grey 原文地址: 博客...
-
5
使用贪心来解决的一些问题 作者:Grey 原文地址: 博客园:...
-
1
2023-08-16:用go写算法。一个坐标可以从 -infinity 延伸到 +infinity 的 无限大的 棋盘上,你的 骑士 驻扎在坐标为 [0, 0] 的方格里。骑士的走法和中国象棋中的马相似,走 “日” 字:即先向左(或右)走 1 格,再向上(或下)走 2 格,...
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK