首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

C的老毛病?用Zig解决

作者丨Aryan Ebrahimpour

策划丨诺亚

C是一种低级系统编程语言,几乎没有对内存的抽象,因此内存管理完全由开发人员自己负责,并且对汇编的抽象最少(但表达能力足以支持一些通用概念,例如类型系统)。它也是一种非常可移植的编程语言,因此如果编写正确,即使它具有一些晦涩的架构,也可以在你的烤面包机上运行。

C的特性使其成为一种非常契合其预期用途的语言。然而,这并不意味着它的设计决策按照今天的标准是完美无缺的。 如今,Zig横空出世,作为一种新的系统编程语言受到了相当多的关注。

Zig将自己定位为更好的C语言。但Zig是如何实现这一目标的呢?在本文中,我们的目的是研究与C相关的一些问题,并探讨Zig打算如何解决这些问题。

目录一览

Comptime文本替换预处理

内存管理和Zig分配器

十亿美元的错误与Zig Optional

指针算术与ZigSlice

显式内存对齐

数组作为值

错误处理

一切都是一种表达

C有更复杂的语法需要处理

Comptime文本替换预处理

使用预处理器替换源代码中的文本并不是C所独有的。它在C创建之前就已经存在,并且可以追溯到早期的示例,例如IBM704 计算机的SAP汇编器。下面是一个AMD64汇编代码片段的示例,它定义了一个pushr宏,并根据其参数将其替换为push或:pushf。

amd64-macro.asm

C是对汇编的最小抽象,采用了相同的方法来支持宏,可以轻松地变成脚枪。举个小例子:

footgun-macro.c

你可能期望这段代码设置to的值。然而,由于宏函数的文本替换性质,展开的结果是,其求值为11,而不是25。(2+ 3)的平方=(2 + 3)^2 = 25SQUARE2 + 3 * 2 + 3

为了使其正确工作,确保所有宏都正确,加上括号至关重要:

C不会容忍这样的错误,也不会好心地通知你。错误可能在很久以后,在程序中完全不相关的部分的另一个输入中显示出来。

另一方面,Zig通过引入参数和函数,为这类任务采用了更加直观的方法。这使我们能够在编译时而不是运行时执行函数。下面是同一个C语言宏在Zig:comptimesSQUARE中

Zig编译器的另一个优点是它能够对输入执行类型检查,即使它是。在使用Zig调用函数时,如果使用的类型不支持该操作符,则会导致编译时类型错误:anytypessquare*

Comptime允许在编译时执行任意代码

comptime-example.zig

这个Zig程序定义了一个fibonacci函数,然后在编译时调用该函数来设置的值foo。Nofibonacci在运行时被调用。

Zig的comptime计算还可以涵盖C语言的一些小特性:例如,在最小值为-2^15=-32768且最大值为(2^15)-1=32767的平台中signed,不可能在C中将类型的最小值写signed为文字常量。

这是因为在C中-32768实际上is-1* 32768并且32768不在signed类型的边界内。然而,在Zig中,-1* 32768是编译时评估。

内存管理和Zig分配器

正如我前面提到的,C语言几乎没有对内存的抽象。这有利有弊:

利:人们可以完全控制内存,可以用它做任何想做的事

弊:人们可以完全控制内存,可以用它做任何想做的事

权力越大,责任越大。在像C这样使用手动内存管理的语言中,内存管理不当可能会导致严重的安全后果。在最好的情况下,它可能导致拒绝服务,在最坏的情况下,它可以让攻击者执行任意代码。许多语言试图通过施加编码限制或使用垃圾收集器消除整个问题来减少这种责任。然而,Zig采用了一种不同的方法。

Zig同时提供了几个优势:

手动内存管理:你做你的。内存的控制权在你手中。没有像Rust那样的编码限制。

没有隐藏分配:在你不知道并允许它发生的情况下,不会在堆上分配任何东西。Zig利用Allocator类型来实现这一点。任何在堆上分配的函数都会接收一个Allocator作为参数。任何不这样做的东西都不会在堆上分配,这是肯定的。

避免内存泄漏的安全工具,例如std.heap.GeneralPurposeAllocator

Zig不像Rust那样限制你的编码方式,帮助你保持安全和避免泄漏,但仍然让你像在C中那样完全随心所欲。我个人认为它可能是一个方便的中间地带。

注意:为了提高可读性,某些路径会用三点缩短

附:Zig提供了几个内置分配器,包括但不限于:

FixedBufferAllocator

GeneralPurposeAllocator

TestingAllocator

c_allocator

StackFallbackAllocator

LoggingAllocator

你总是可以实现自己的分配器。

十亿美元的错误与Zig Optional

这段C代码突然崩溃,除了让你知道SIGSEGV到底发生了什么之外,没有任何线索:

另一方面,Zig没有任何参考资料。它具有可选类型,在开头用问号表示。只能给可选类型赋值,并且只能在使用关键字或简单地通过表达式检查它们是否为null时引用它们(null引用曾被快速排序算法的创造者托尼·霍尔称为"十亿美元错误")。否则,你将最终面临编译错误。

指针算术与Zig Slice

在C语言中,地址被表示为一个数值,这使得开发人员可以对指针执行算术运算。该特性使C开发人员能够通过操作地址来访问和修改任意内存位置。

指针算术通常用于操作或访问数组的特定部分或有效地在动态分配的内存块中导航等任务,而不需要复制。然而,由于C语言的无情本质,指针算术很容易导致诸如分段错误或未定义行为等问题,从而使调试成为真正的痛苦。

大多数此类问题可以使用Slices来解决。切片提供了一种更安全、更直观的方式来操作和访问数组或内存部分:

显式内存对齐

每种类型都有一个对齐号,它定义了该类型合法的内存地址。对齐以字节为单位,它确保变量的起始地址可以被对齐值整除。例如:

该u8类型的自然对齐方式为1,这意味着它可以驻留在任何内存地址中。

该u16类型具有2的自然对齐方式,这意味着它只能驻留在地址可被2整除的内存位置中,例如0、2、4、6、8等...

该u32类型具有4的自然对齐方式,这意味着它只能驻留在地址可被4整除的内存位置中,例如0、4、8、12、16等...

CPU强制执行这些对齐要求。如果变量的类型未正确对齐,可能会导致程序崩溃(例如分段错误)或导致非法指令。

现在我们将unsignedint在下面的代码中故意创建一个指向an的未对齐指针。此代码将在大多数CPU上运行时崩溃:

使用低级语言会带来其自身的挑战,例如管理内存对齐。犯错误可能会导致崩溃,而C对此无能为力。Zig呢?让我们在Zig中编写类似的代码:

如果你编译上面的代码,Zig会抱怨并阻止编译,因为存在对齐问题:

即使你尝试使用显式欺骗zig@alignCast,Zig也会在安全构建模式下向生成的代码添加指针对齐安全检查,以确保指针按照承诺对齐。因此,如果运行时对齐错误,它会出现恐慌,并显示一条消息和跟踪信息,以便你了解问题出在哪里。这是C不会为你做的事情:

在运行时你将收到:

数组作为值

C语言的语义定义了数组总是作为引用传递

C中的解决方案是创建一个包装器结构并传递该结构:

在Zig中它就可以工作

错误处理

许多Capi都有错误码的概念,其中函数的返回值要么表示成功状态,要么表示发生的特定错误的整数。

Zig使用相同的方法来处理错误,但是通过在类型系统中以更有用和更具表现力的方式捕获错误,改进了这个概念。

Zig中的错误集类似于枚举。但是,整个编译过程中的每个错误名称都会被分配一个大于0的无符号整数。

错误集类型和正常类型可以使用!操作符用于形成错误联合类型(例如:FileOpenError!u16)。这些类型的值可能是错误值,也可能是正常类型的值。

Zig确实有trycatch关键字,但它们与其他语言无关,因为Zig没有例外

Tryx是,的快捷方式,xcatch |err| return err通常用于不适合处理错误的地方。

总的来说,Zig的错误处理机制类似于C,但有类型系统的支持。

一切都是一种表达

从高级语言到C语言,你可能会错过以下功能:

IIFE.js

Zig的美妙之处在于,你可以将Zig块当作表达式来操作。

再举一个更复杂的示例:

每个块都可以有一个标签,例如:blk和break从该块breakblk:返回一个值。

C有更复杂的语法需要处理

看看这个C类型:

这声明bar为指向返回char常量指针的函数(int)的指针的数组5的常量指针。不管什么意思。

甚至还有像cdecl.org这样的工具可以帮助你阅读C类型并为你人性化。我很肯定,对于实际的C开发人员来说,处理此类类型可能并不那么具有挑战性。有些人有幸拥有这种能力,能够阅读神的语言。但对于像我这样宁愿让事情变得简单的人来说,Zig类型更容易阅读和维护。

结论

在这篇博文中,我们讨论了C语言的一些问题,这些问题导致人们寻找或创建替代过去遗留下来的语言。

总之,Zig通过以下方式解决了这些问题:

ZigComptimes

Zig分配器

ZigOptionals

ZigSlices

Zig显式对齐

Zig阵列

Zig错误类型

Zig表达式

https://avestura.dev/blog/problems-of-c-and-how-zig-addresses-them

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OsXWF7NNtHU66LNnZAqLX0GA0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券