首页
技术小册
AIGC
面试刷题
技术文章
MAGENTO
云计算
视频课程
源码下载
PDF书籍
「涨薪秘籍」
登录
注册
01|快速回顾:一个 C 程序的完整生命周期
02|程序基石:数据与量值是如何被组织的?
03|计算单元:运算符是如何工作的?
04|控制逻辑:表达式和语句是如何协调程序运行的?
05|代码封装:函数是如何被调用的?
06|整合数据:枚举、结构与联合是如何实现的?
07|操控资源:指针是如何灵活使用内存的?
08|编译准备:预处理器是怎样处理程序代码的?
09|标准库:字符、字符串处理与数学计算
10|标准库:深入理解标准 IO
11|标准库:非本地跳转与可变参数是怎样实现的?
12|标准库:你需要了解的 C 并发编程基础知识有哪些?
13|标准库:如何使用互斥量等技术协调线程运行?
14|标准库:信号与操作系统软中断有什么关系?
15|标准库:日期、时间与实用函数
16|标准库:断言、错误处理与对齐
17|极致优化:如何实现高性能的 C 程序?
18|生产加速:C 项目需要考虑的编码规范有哪些?
19|生产加速:如何使用自动化测试确保 C 项目质量?
20|生产加速:如何使用结构化编译加速 C 项目构建?
21|实战项目:一个简单的高性能 HTTP Server
22|可执行二进制文件里有什么?
23|进程是如何使用操作系统内存的?
24|编译器在链接程序时发生了什么?
25|程序可以在运行时进行链接吗?
26|C 程序的入口真的是 main 函数吗?
27|ABI 与 API 究竟有什么区别?
28|程序如何与操作系统交互?
当前位置:
首页>>
技术小册>>
深入C语言和程序运行原理
小册名称:深入C语言和程序运行原理
### 第十一章 标准库:非本地跳转与可变参数是怎样实现的? 在C语言及其标准库中,非本地跳转(Non-local Jumps)和可变参数(Variadic Functions)是两种高级特性,它们为程序员提供了灵活控制程序流程和处理不确定数量参数的能力。这些特性在错误处理、递归深度控制、以及设计通用函数接口时尤为重要。本章将深入探讨这两种机制在C语言标准库中的实现原理,以及它们如何被用于解决实际编程问题。 #### 11.1 非本地跳转:`setjmp` 与 `longjmp` 非本地跳转允许程序从当前执行点直接跳转到另一个代码段中的特定位置,这种跳转不受常规函数调用和返回机制的约束。C语言标准库通过`setjmp`和`longjmp`函数实现了这一功能。这种机制通常用于错误处理,尤其是在深层嵌套的函数调用中,能够快速跳出多层调用栈,回到一个已知的安全点。 ##### 11.1.1 `setjmp` 函数 `setjmp`函数用于设置跳转的目标点。它通常与`jmp_buf`类型的变量一起使用,该变量用于保存调用`setjmp`时的环境(如寄存器状态、栈帧信息等),以便之后通过`longjmp`恢复。`setjmp`的原型定义在`<setjmp.h>`头文件中: ```c #include <setjmp.h> int setjmp(jmp_buf env); ``` 当首次调用`setjmp`时,它返回0,并将当前的环境保存到`jmp_buf`参数指向的缓冲区中。如果之后在同一作用域内(通常是同一函数或更深层的嵌套函数中)调用了`longjmp`并指定了相同的`jmp_buf`,则`setjmp`将返回非零值(通常是`longjmp`的第二个参数),并恢复到`setjmp`调用时的环境继续执行。 ##### 11.1.2 `longjmp` 函数 `longjmp`函数用于执行非本地跳转。它接受两个参数:一个`jmp_buf`类型的变量,用于指定跳转的目标环境;一个整型值,该值将作为`setjmp`的返回值。`longjmp`的原型如下: ```c #include <setjmp.h> void longjmp(jmp_buf env, int val); ``` 调用`longjmp`后,程序将立即停止当前执行路径,恢复到与`jmp_buf`关联的`setjmp`调用点,并继续执行,此时`setjmp`返回`longjmp`提供的整数值。需要注意的是,`longjmp`会跳过所有中间函数的正常返回过程,这可能导致资源泄露(如未释放的内存、未关闭的文件句柄等),因此使用时需格外小心。 ##### 11.1.3 实现细节与限制 `setjmp`和`longjmp`的实现依赖于底层操作系统的支持,特别是关于如何保存和恢复程序执行环境。由于这种机制绕过了正常的函数调用和返回机制,它可能导致一些难以预测的行为,比如局部变量的值可能不是预期的(因为它们可能已被覆盖或未初始化)。此外,`setjmp`和`longjmp`不能跨函数作用域使用,即`jmp_buf`必须在`setjmp`和`longjmp`的调用之间保持有效。 #### 11.2 可变参数:`stdarg.h` 在C语言中,函数通常具有固定数量的参数。然而,有时我们需要设计能够接收任意数量参数的函数,比如`printf`。为了实现这一功能,C标准库提供了可变参数(Variadic Functions)的支持,主要通过`<stdarg.h>`头文件中的宏和类型定义来实现。 ##### 11.2.1 可变参数函数的声明 可变参数函数在声明时,其参数列表以省略号(`...`)结束。例如,`printf`函数的声明如下: ```c int printf(const char *format, ...); ``` ##### 11.2.2 `stdarg.h`中的宏和类型 为了访问可变参数,`<stdarg.h>`提供了几个宏和类型定义: - `va_list`:用于声明一个变量,该变量将用于遍历参数列表。 - `va_start`:初始化`va_list`变量,准备访问参数列表。 - `va_arg`:访问参数列表中的下一个参数,并返回其值。 - `va_end`:清理与`va_list`变量相关的资源(尽管在许多实现中,这一步是可选的)。 ##### 11.2.3 使用示例 下面是一个简单的可变参数函数示例,该函数计算并返回所有传入整数的和: ```c #include <stdarg.h> #include <stdio.h> int sum(int num, ...) { va_list args; int total = 0; va_start(args, num); // num是最后一个固定参数 for (int i = 0; i < num; i++) { total += va_arg(args, int); // 依次获取每个整数参数 } va_end(args); return total; } int main() { printf("%d\n", sum(3, 1, 2, 3)); // 输出6 return 0; } ``` ##### 11.2.4 实现细节与注意事项 可变参数的实现依赖于编译器对函数参数传递方式的了解。在大多数现代平台上,参数通过栈传递,`va_list`和相关的宏实际上是对栈操作的封装。然而,由于栈的具体实现可能因编译器和平台而异,因此编写可移植的可变参数函数时需要格外小心。 此外,使用`va_arg`时需要注意类型安全。由于`va_arg`本身不进行类型检查,如果提供的类型与实际参数不匹配,可能会导致未定义行为。因此,在编写可变参数函数时,通常需要在函数文档中明确说明参数的类型和顺序。 #### 总结 非本地跳转和可变参数是C语言标准库中提供的两种强大特性,它们分别用于处理复杂的错误恢复和灵活的函数参数传递。通过深入了解这些特性的实现原理和使用方法,程序员可以更加高效地编写出健壮、灵活和可维护的代码。然而,由于这些特性可能引入额外的复杂性和风险,因此在使用时需要谨慎考虑,并遵循最佳实践。
上一篇:
10|标准库:深入理解标准 IO
下一篇:
12|标准库:你需要了解的 C 并发编程基础知识有哪些?
该分类下的相关小册推荐:
c++零基础入门