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

04|控制逻辑:表达式和语句是如何协调程序运行的?

空语句 ; 在C语言中的使用方式
大多以类似的方式实现
改变程序执行流程的语法结构
包括break、continue、return、goto
通过基本的测试与条件跳转实现
包括do...while、for、while
编译器实现方式包括位映射法、跳表法、基于二分法的测试与条件跳转语句
包括if...else和switch...case
不同类型的语句在执行时可能产生副作用
语句必须以分号结尾,并按从上到下的顺序依次执行
包括复合语句、表达式语句、选择语句、迭代语句、跳转语句
描述程序的基本构建块
表达式提供了数据同时参与多个操作符不同计算过程的能力
求值顺序体现在源码对应的抽象语法树(AST)上
求值过程实际上是对表达式和子表达式进行递归求值的过程
求值过程依据运算符的优先级和结合性
由一系列运算符与操作数组成的语法结构
思考题
跳转语句
迭代语句
选择语句
语句
表达式
C语言中表达式和语句的协调程序运行

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

你好,我是于航。今天,我们继续来看 C 基本语法结构背后的实现细节。
上一讲,我主要介绍了编译器是如何使用机器指令来实现各类 C 运算符的。在应用程序的构建过程中,运算符仅作为“计算单元”,为程序提供了基本的“原子”计算能力。而数据如何同时使用多种不同的运算符,以及按照怎样的逻辑来在不同位置上“流动”,这一切都是由表达式和语句进行控制的。这一讲,就让我们来看看 C 语言中,用来描述程序运行逻辑的这两种控制单元“背后的故事”。

表达式

表达式(expression)是由一系列运算符与操作数(operand)组成的一种语法结构。其中,操作数是参与运算符计算的独立单元,也即运算符所操作的对象。操作数可以是一个简单的字面量值,比如数字 2、字符串 “Hello, world!”;也可以是另一组复杂的表达式。举个例子:在表达式 (1 + 2) * 3 + 4 / 5 中,乘法运算符 “*” 所对应的两个操作数分别是字面量数值 3,和子表达式 (1 + 2)
通常来说,表达式的求值(evaluation)过程需要依据所涉及运算符的优先级和结合性的不同,而按一定顺序进行。我们一起来看看上面提到的 (1 + 2) * 3 + 4 / 5 这个表达式的计算流程。
首先,需要根据表达式中运算符优先级的不同,来决定最先进行哪一部分运算。运算符的优先级很好理解,由于乘法运算符 “*” 与除法运算符 “/” 的优先级高于加法运算符 “+”,因此在计算整个表达式的值时,需要首先对由这两个运算符组成的子表达式进行求值。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

C语言中的表达式和语句是程序运行的关键控制单元。表达式由运算符和操作数组成,通过优先级和结合性决定计算顺序,编译器将其映射到抽象语法树上进行递归求值。语句是程序的基本构建块,包括复合语句和表达式语句等类型,以及选择、迭代和跳转语句。这些语句按顺序执行,构成程序的逻辑流程。 在选择语句部分,文章介绍了if...else和switch...case语句的编译器实现细节,包括汇编代码的分析和优化条件下的实现策略。在迭代语句部分,以do...while语句为例,讲解了迭代过程的汇编实现方式。跳转语句部分介绍了break、continue、return和goto语句的功能及其在程序执行流程中的作用。 总的来说,文章通过深入分析C语言中表达式和语句的实现细节,帮助读者更好地理解程序的执行流程和编译器的优化策略。对于想深入了解C语言底层实现的读者来说,这篇文章提供了宝贵的技术细节和实际示例,有助于加深对C语言程序执行机制的理解。

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

全部留言(16)

  • 最新
  • 精选
  • 置顶
    现在遇到了 汇编指令 比较多了,有些指令不知道做了什么, 有没有一个统一的地方可以方便进行查询的

    作者回复: 这是一个好问题,可以 watch 一下我在 01 讲的提到的 GitHub 仓库哈,我会尽快把相关资料更新在里面的。

    2021-12-19
    2
    4
  • liu_liu
    关于迭代语句中的 .L2 汇编代码段: // 把 v 放入 eax mov eax, DWORD PTR [rbp-4] // edx = rax-1 lea edx, [rax-1] // 把 edx 的值写入 rbp-4 地址 move DWROD PTR [rbp-4], edx 这段汇编的作用应该是用于 v-- 。 但有一些不太明白,为什么要使用 lea 指令呢?可直接用 move 指令?

    作者回复: 这是一个很棒的问题! 如果使用 mov 指令,也是可以完成同样的功能的,只不过需要独立的两条指令,比如: - dec rax; - mov edx, rax 但使用 lea 指令,我们就可以仅通过一条机器指令来实现。相对于前者,其执行效率是不是更高呢?实际上,对于编译器来说,很多基本的四则运算都可以直接使用 lea 指令来实现,比如 lea eax, [123 + 4*ebx + esi],就是直接将 ebx 中的值乘以 4 再加上 123 和 esi 中的值。相较于使用 mov 的多条指令,lea 可以充分利用它在硬件中的电路特性,以更高的效率进行计算。

    2021-12-15
    10
  • pedro
    竟然有一种像当年读CSAPP第三章的感觉! 空语句用的少,多数时候用来做空循环体吧。

    作者回复: 没错!这是一种常见的场景。举个例子: #include <stdio.h> int main() { char c; while ((c = getchar()) == ' '); printf("%c", c); }

    2021-12-13
    6
  • qinsi
    维基上Duff's device的例子 send(to, from, count) register short *to, *from; register count; { register n = (count + 7) / 8; switch (count % 8) { case 0: do { *to = *from++; case 7: *to = *from++; case 6: *to = *from++; case 5: *to = *from++; case 4: *to = *from++; case 3: *to = *from++; case 2: *to = *from++; case 1: *to = *from++; } while (--n > 0); } }

    作者回复: 达夫设备是 switch 语句和循环展开的巧妙结合,我们会在 19 讲再来深入介绍哈!

    2021-12-13
    3
  • 送过快递的码农
    老师 ,test/cmp 比较多指令,最终结果会在EFLAGS这个寄存器中么,j(cond)指令会根据这个寄存器做相应的跳动?

    作者回复: 没错的,你的理解是正确的。

    2021-12-15
    2
  • 阿妞
    老师,请问一下 下面代码的汇编,满足条件跳转到.L6,不满足就继续执行,但最后的v=20,是在.L6里面,只有跳转才执行,不跳转就不执行?从c上看,是无论满足不满足条件,都应该执行的呀,很疑惑! int test() { int v=20; if(v>10) { v--; v=10; } v=20; } test: push rbp mov rbp, rsp mov DWORD PTR [rbp-4], 20 cmp DWORD PTR [rbp-4], 10 jle .L6 sub DWORD PTR [rbp-4], 1 mov DWORD PTR [rbp-4], 10 .L6: mov DWORD PTR [rbp-4], 20 nop pop rbp ret

    作者回复: 不跳转就会继续顺序执行,也是会执行到 .L6 位置的代码的。

    2022-08-31归属地:江苏
    2
  • 白花风信子
    进程里面的挂起也是for(;;);叭

    作者回复: 如果这里提到的“挂起”是指阻塞当前线程的话,可以使用 poll \ select \ semaphore 等方式哈。死循环并不是一个好的方式。

    2021-12-18
  • 像 edi esi 对应的应该是 第一个参数 第二个参数吧 ~ 我记得有 6个还是7个寄存器可以用来传参

    作者回复: 没错的,我们在第 05 讲有介绍这些内容哈。

    2021-12-17
    2
  • chinandy
    老师:高优化等级怎么打开,在您给的那个网站上

    作者回复: 可以直接在上方的 “Compiler options” 输入框里输入对应优化等级的参数,比如:-O2 \ -O3。

    2021-12-14
  • Geek_828b39
    将寄存器 rdx 中的值左移 v 位(值被扩展为 64 位); 老师,这个这么理解?

    作者回复: 这里实际上是跟蓝框内的第一条汇编语句做的对比。第一条的 mov 指令仅把值 1 当做 32 位值放入到 edx 中;而后续程序在使用这个值生成“token”时,实际上是直接用 rdx 来访问的,因此实际上是在进行左移时同时被“扩展”为了 64 位值。同样的,后面进行按位与时,也是直接以 64 位值进行的。

    2021-12-14
收起评论
显示
设置
留言
16
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部