深入 C 语言和程序运行原理
于航
PayPal 技术专家
21121 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
深入 C 语言和程序运行原理
15
15
1.0x
00:00/00:00
登录|注册

06|代码封装(下):函数是如何被调用的?

递归调用语句必须作为函数返回前的最后一条语句
提高函数执行性能
避免栈溢出问题
减少函数调用栈帧的创建与销毁次数
不要编写依赖于特定参数求值顺序的代码逻辑
不固定
函数声明、定义、调用三者的参数列表必须保持一致
ANSI C 标准提出了新的“函数原型”概念
容易引入难以调试的程序问题
废弃的 K&R 函数声明
尾递归调用优化
参数求值顺序
C 函数调用规范

该思维导图由 AI 生成,仅供参考

你好,我是于航。
在上一讲中,我们主要围绕着 x86-64 平台上 C 函数被调用时需要遵循的一系列规则,即 System V AMD64 ABI 调用规范的内容展开了深入的探讨。而今天,我们将继续讨论有关 C 函数的话题,来看看参数求值顺序、递归调用、旧式声明的相关内容。这些内容将会帮助你更加深入地理解 C 函数的运作细节,写出更加健壮、性能更高的代码。

编写不依赖于参数求值顺序的函数

当一个函数被调用时,传递给它的实际参数应该按照怎样的顺序进行求值,这在 C 标准中并没有被明确规定。因此,对于某些特殊的代码形式,当运行使用不同编译器编译得到的二进制可执行文件时,可能会得到不同的计算结果。比如下面这段代码:
#include <stdio.h>
int main(void) {
int n = 1;
printf("%d %d %d", n++, n++, n++);
return 0;
}
这里,我们使用 printf 函数,连续打印出了表达式 n++ 的值。当使用 Clang 13.0.0 编译器进行编译并运行这段代码时,可以得到输出结果 “1 2 3”。而换成 GCC 11.2 时,则得到了不同的结果 “3 2 1”。通过查看汇编代码,我们能够看到:Clang 按照从左到右的顺序来依次计算表达式 n++ 的值,而 GCC 则与之相反。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

C语言函数调用内部机制是程序员必须了解的重要知识之一。本文深入探讨了C函数调用的关键概念,包括参数求值顺序、尾递归调用优化和K&R函数声明。首先,文章指出了函数参数求值顺序的不确定性可能导致不同编译器得到不同的计算结果,强调了编写不依赖于参数求值顺序的函数的重要性。其次,文章介绍了尾递归调用优化,解释了其原理和条件,以及对程序性能的影响。此外,文章还讨论了K&R函数声明可能引入的问题,并提出了使用函数原型的建议。总的来说,本文通过深入的技术讨论,帮助读者更好地理解C函数调用的内部工作原理,并指导他们编写更加健壮、性能更高的代码。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《深入 C 语言和程序运行原理》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(12)

  • 最新
  • 精选
  • 赵岩松
    连续看到现在,受益良多,非常感谢!

    作者回复: 很高兴对你有帮助 : )

    2021-12-19
    2
  • dog_brother
    老师,我有个问题,尾递归优化的条件:递归调用语句必须作为函数返回前的最后一条语句。老师,我对这句话理解不是很深入,可以举一个不能使用尾递归优化的递归代码么?

    作者回复: 需要注意的是“递归调用语句必须作为函数返回前的最后一条语句”这个条件只是我们根据尾递归优化的原理,来为编译器能够进行优化所做的假设。而实际编译器是否可以通过其他方式做到“将函数递归调用优化为循环”,这个就属于编译器本身的能力范畴了。而之所以有这样的假设,是由于相较于函数的递归调用,循环只能够发生在同一个函数栈帧的环境中。因此,对于所有在函数返回前产生的中间变量值,实际上都无法被正常保存。而如果函数的正常执行依赖于这些中间结果,则尾递归优化将无法进行。比如这个例子: int factorial(int num) { if (num == 1 || num == 0) return 1; return num * factorial(num - 1); } 虽然其实现不满足尾递归优化的前提要求,但编译器却可在高优化等级下将它的实现由递归变为迭代。

    2022-02-07
  • zxk
    _Noreturn 也等价于 noreturn,方法声明该关键字则表示方法调用后不再返回,可用于声明一些异常退出的方法。 这个特性的主要目的在于提高方法的可读性,同时还能够借助编译器,提前检测出 unreachable 的代码。
    2022-01-20
    5
  • Luke
    1.尾递归优化其实就是编译器帮忙把递归改成了迭代,不过需要代码写的便于编译器去优化。递归函数的最后一条语句是其本身即可,这样的形式可以帮编译器确认本次调用结果不依赖函数内的中间结果。其实很好理解,迭代的写法里面,单次循环也不依赖于上次的循环结果。当然,现代编译器越来越强大了,写的差点也能优化成尾递归。 2.k&r的古老声明方式还真没有用过,倒是无参函数最好在入参部分指定void的写法经常用。结合c的历史和自己的经验,这一条主要是想给维护者明确的信号,没有手误,我确定这是无参函数。 3.跟上述同理,noreturn应该也是让外部明确本函数没有返回值,不要依赖于他的返回值,甚至如果有使用该函数返回值的语句,编译器也会给出error。
    2022-09-02归属地:江苏
    1
    1
  • qinsi
    就是__attribute__((noreturn)),比如在一个有返回值的函数里调用了exit()之后程序就退出了,如果exit没有声明为noreturn的话编译器就会警告说调用了exit的函数没有返回值。在其他语言里noreturn通常被称为Never类型。
    2021-12-19
    1
  • Geek_00a01d
    思考题 交作业 C11 中新引入了一个名为 _Noreturn 的关键字 ---- 顾名思义 应该是没有返回值吧 大部分返回值为void的函数执行到函数最后一行代码就会返回调用者,继续执行调用者的逻辑 使用__Noreturn貌似不会继续执行调用者的逻辑
    2022-12-17归属地:河南
  • 青鸟飞鱼
    这才是C语言,我以前学的真的是皮毛都不算。
    2022-04-30
  • 小杰
    https://docs.microsoft.com/zh-cn/cpp/c-language/noreturn?view=msvc-170。查看微软中文文档,得到的答案:_Noreturn 关键字在 C11 中引入。 它告知编译器,应用编译器的函数不返回调用方。使用 _Noreturn(或等效的 noreturn)的主要好处是在代码中明确函数的意向,便于将来的读者了解,以及检测意外产生的无法访问的代码。标记为 noreturn 的函数不应包括返回类型,因为它不会将值返回给调用方。 它应为 void。
    2022-04-19
  • Tokamak
    经过实验 int fib(int n, int acc) { if(n <= 1) return acc; int a = 10, b = 20; // 无用语句,用于测试尾递归 return fib(n - 1, acc * n); } 在O3优化等级下也可以进行尾递归优化。gcc可真厉害~
    2022-04-11
  • =
    使用_Noreturn声明的函数不会返回到调用它的函数中,若是在其中使用了return语句,会在编译时报错。
    2021-12-19
收起评论
显示
设置
留言
12
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部