c代码是如何跑起来的(代码片段)

doujiang24 doujiang24     2022-12-10     182

关键词:

上一篇「CPU 提供了什么」中,我们了解了物理的层面的 CPU,为我们提供了什么。

本篇,我们介绍下高级语言「C 语言」是如何在物理 CPU 上面跑起来的。

C 语言提供了什么

C 语言作为高级语言,为程序员提供了更友好的表达方式。在我看来,主要是提供了以下抽象能力:

  1. 变量,以及延伸出来的复杂结构体
    我们可以基于变量来描述复杂的状态。
  2. 函数
    我们可以基于函数,把复杂的行为逻辑,拆分到不同的函数里,以简化复杂的逻辑以。以及,我们可以复用相同目的的函数,现实世界里大量的基础库,简化了程序员的编码工作。

示例代码

构建一个良好的示例代码,可以很好帮助我们去理解。
下面的示例里,我们可以看到 变量 和 函数 都用上了。

#include "stdio.h"

int add (int a, int b) 
    return a + b;


int main () 
    int a = 1;
    int b = 2;
    int c = add(a, b);

    printf("a + b = %d\\n", c);

    return 0;

编译执行

毫无意外,我们得到了期望的 3

$ gcc -O0 -g3 -Wall -o simple simple.c
$ ./simple
a + b = 3

汇编代码

我们还是用 objdump 来看看,编译器生成了什么代码:

  1. 变量
    局部变量,包括函数参数,全部被压入了 栈 里。
  2. 函数
    函数本身,被单独编译为了一段机器指令
    函数调用,被编译为了 call 指令,参数则是函数对应那一段机器指令的第一个指令地址。
$ objdump -M intel -j .text -d simple

# 截取其中最重要的部分

000000000040052d <add>:
  40052d:       55                      push   rbp
  40052e:       48 89 e5                mov    rbp,rsp
  400531:       89 7d fc                mov    DWORD PTR [rbp-0x4],edi
  400534:       89 75 f8                mov    DWORD PTR [rbp-0x8],esi
  400537:       8b 45 f8                mov    eax,DWORD PTR [rbp-0x8]
  40053a:       8b 55 fc                mov    edx,DWORD PTR [rbp-0x4]
  40053d:       01 d0                   add    eax,edx
  40053f:       5d                      pop    rbp
  400540:       c3                      ret

0000000000400541 <main>:
  400541:       55                      push   rbp
  400542:       48 89 e5                mov    rbp,rsp
  400545:       48 83 ec 10             sub    rsp,0x10
  400549:       c7 45 fc 01 00 00 00    mov    DWORD PTR [rbp-0x4],0x1
  400550:       c7 45 f8 02 00 00 00    mov    DWORD PTR [rbp-0x8],0x2
  400557:       8b 55 f8                mov    edx,DWORD PTR [rbp-0x8]
  40055a:       8b 45 fc                mov    eax,DWORD PTR [rbp-0x4]
  40055d:       89 d6                   mov    esi,edx
  40055f:       89 c7                   mov    edi,eax
  400561:       e8 c7 ff ff ff          call   40052d <add>
  400566:       89 45 f4                mov    DWORD PTR [rbp-0xc],eax
  400569:       8b 45 f4                mov    eax,DWORD PTR [rbp-0xc]
  40056c:       89 c6                   mov    esi,eax
  40056e:       bf 20 06 40 00          mov    edi,0x400620
  400573:       b8 00 00 00 00          mov    eax,0x0
  400578:       e8 93 fe ff ff          call   400410 <printf@plt>
  40057d:       b8 00 00 00 00          mov    eax,0x0
  400582:       c9                      leave
  400583:       c3                      ret
  400584:       66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
  40058b:       00 00 00
  40058e:       66 90                   xchg   ax,ax

函数内的局部变量,为什么会放入栈空间呢?

这个刚好和局部变量的作用域关联起来了:

  1. 函数执行结束,返回的时候,局部变量也应该失效了
  2. 函数返回的时候,刚好要恢复栈高度到上一个调用者函数。

这样的话,只需要栈高度恢复,也就意味着被调用函数的所有的临时变量,全部失效了。

函数内的局部变量,一定会放入栈空间吗?

答案是,不一定。
上面我们是通过 -O0 编译的,接下来,我们看下 -O1 编译生成的机器码。

此时的局部变量直接放在寄存器里了,不需要写入到栈空间了。
不过,此时 main 都已经不再调用 add 函数了,因为已经被 gcc 内联优化了。
好吧,构建个合适的用例也不容易。

000000000040052d <add>:
  40052d:       8d 04 37                lea    eax,[rdi+rsi*1]
  400530:       c3                      ret

0000000000400531 <main>:
  400531:       48 83 ec 08             sub    rsp,0x8
  400535:       be 03 00 00 00          mov    esi,0x3
  40053a:       bf f0 05 40 00          mov    edi,0x4005f0
  40053f:       b8 00 00 00 00          mov    eax,0x0
  400544:       e8 c7 fe ff ff          call   400410 <printf@plt>
  400549:       b8 00 00 00 00          mov    eax,0x0
  40054e:       48 83 c4 08             add    rsp,0x8
  400552:       c3                      ret
  400553:       66 2e 0f 1f 84 00 00    nop    WORD PTR cs:[rax+rax*1+0x0]
  40055a:       00 00 00
  40055d:       0f 1f 00                nop    DWORD PTR [rax]

禁止内联优化

我们用如下命令,关闭 gcc 的内联优化:

gcc -fno-inline -O1 -g3 -Wall -o simple simple.c

再来看下汇编代码,此时的机器码就符合理想的验证结果了。

000000000040052d <add>:
  40052d:       8d 04 37                lea    eax,[rdi+rsi*1]
  400530:       c3                      ret

0000000000400531 <main>:
  400531:       48 83 ec 08             sub    rsp,0x8
  400535:       be 02 00 00 00          mov    esi,0x2
  40053a:       bf 01 00 00 00          mov    edi,0x1
  40053f:       e8 e9 ff ff ff          call   40052d <add>
  400544:       89 c6                   mov    esi,eax
  400546:       bf f0 05 40 00          mov    edi,0x4005f0
  40054b:       b8 00 00 00 00          mov    eax,0x0
  400550:       e8 bb fe ff ff          call   400410 <printf@plt>
  400555:       b8 00 00 00 00          mov    eax,0x0
  40055a:       48 83 c4 08             add    rsp,0x8
  40055e:       c3                      ret
  40055f:       90                      nop

总结

  1. 对于 C 语言的变量,编译器会为其分配一段内存空间来存储
    函数内的局部变量,放入栈空间是理想的映射方式。不过编译的优化模式下,则会尽量使用寄存器来存储,寄存器不够用了,才会使用栈空间。
    全局变量,则有对应的内存段来存储,这个以后可以再聊。
  2. 对于 C 语言的函数,编译器会编译为独立的一段机器指令
    调用该函数,则是执行 call 指令,意思是接下来跳转到执行这一段机器指令。

c程序是如何跑起来的01——普通可执行文件的构成(代码片段)

学习目的程序烧到什么地方?程序加载到内存什么地方?程序如何执行?一、编译环境搭建ubuntu20.04使用arm-linux-gnueabihf-gcc7.5.0。二、程序源码main.c:#include<stdio.h>#include"calc.h"intmain(intargc,char*argv[])in 查看详情

c程序是如何跑起来的01——普通可执行文件的构成(代码片段)

学习目的程序烧到什么地方?程序加载到内存什么地方?程序如何执行?一、编译环境搭建ubuntu20.04使用arm-linux-gnueabihf-gcc7.5.0。二、程序源码main.c:#include<stdio.h>#include"calc.h"intmain(intargc,char*argv[])in 查看详情

简单!代码原来是这样被cpu跑起来的(代码片段)

CPU对我们来说既熟悉又陌生,熟悉的是我们知道代码是被CPU执行的,当我们的线上服务出现问题时可能首先会查看CPU负载情况。陌生的是我们并不知道CPU是如何执行代码的,它对我们的代码做了什么。本文意在简单解... 查看详情

go程序是如何跑起来的(代码片段)

转,原文: https://www.cnblogs.com/qcrao-2018/archive/2019/07/03/11124360.html——————————————————————————————————————————————————————————————————————... 查看详情

我用c语言把何同学的代码跑起来了(代码片段)

@TOC免责声明:仅供娱乐,只是展示这段代码在理论上是可行的。原版代码首先,先来看下视频中何同学的这两段代码:代码分析首先呢,根据图片中的这两段代码,我猜测他可能是想获取数组中第0,1,5,6,10,51,56,58,64号... 查看详情

我用c语言把何同学的代码跑起来了(代码片段)

我用c语言把何同学的代码跑起来了原版代码代码分析代码实现代码执行结果免责声明:仅供娱乐,只是展示这段代码在理论上是可行的。原版代码首先,先来看下视频中何同学的这两段代码:代码分析首先呢࿰... 查看详情

简单!代码原来是这样被cpu跑起来的(代码片段)

CPU对我们来说既熟悉又陌生,熟悉的是我们知道代码是被CPU执行的,当我们的线上服务出现问题时可能首先会查看CPU负载情况。陌生的是我们并不知道CPU是如何执行代码的,它对我们的代码做了什么。本文意在简单解... 查看详情

一个程序怎么跑起来的(代码片段)

#include<stdio.h>intmain()printf("hello,world\\n");return0;在Unix系统上,由编译器把源文件转换为目标文件。gcc-ohellohello.c 查看详情

c代码如何跑起来(程序编译和预处理)

c代码1.集成开发环境(IDE)2.如何跑起来3.以VS为例1.集成开发环境(IDE)集成开发环境包含如下编辑器:用来编写代码,并且给代码着色,以方便阅读;代码提示器:输入部分代码,即可提... 查看详情

debeziumconnector是怎么在kafkaconnect中跑起来的?(代码片段)

主要脉络KafkaConnect在启动的过程中,会加载connector,并读取taskconfig,开启task,其中taskconfig中有一key为”task.class”,对于debeziumMysqlConnector,它的值为”io.debezium.connector.mysql.MySqlConne 查看详情

canal源码解析系列-先把demo跑起来(代码片段)

写在前面把demo跑起来,一个是对canal的功能有个整体的认识,还有就是阅读源码过程中如果有看不懂的地方可以debug下。环境准备zk,安装后启动开启2181端口,具体过程省略。mysql,我本地自己装了一个,... 查看详情

先让springbootmybatisplus跑起来(代码片段)

架空一切前提条件,只讲怎么让它跑起来,实现基本的增删改查操作!架空一切前提条件,只讲怎么让它跑起来,实现基本的增删改查操作!假设:1、MySQL里已经有一张user表;2、已经安装IDEA;3、已经新建一个SpringBoot项目;4、... 查看详情

如何让dotnetcore在linux上后台运行?(代码片段)

...;场景是这样的,我的开发环境是windows,每次发布代码时我会使用vs的publish发布代码,然后将代码copy到AWSEC2上,然后用dotnet命令将程序跑起来。sudo dotnet application.dll程序是可以跑起来,但我 查看详情

计算机是怎么跑起来的?(代码片段)

...鸦雀无声甚至气氛一度及其尴尬。作为一个CURD男孩,写代码就是一把梭复制粘贴,那能管那么宽?仔细一想,我也是学过计算机组成原理、操作系统原理的男孩,岂能说怂就怂?这时,冯·诺依曼、寄存器、内存、二进制、补码... 查看详情

让flutter在鸿蒙系统上跑起来(代码片段)

前言鸿蒙系统(HarmonyOS)是华为推出的一款面向未来、面向全场景的分布式操作系统。在传统单设备系统能力的基础上,鸿蒙提出了基于同一套系统能力、适配多种终端形态的分布式理念。自2020年9月HarmonyOS2.0发布以来,华为加... 查看详情

vscode代码修改后跑起来没反应,打开本地文件,代码没变化(代码片段)

  解决办法: 修改VSCode默认配置文件,点击左下角设置标志图->设置,出来了设置相关的东西,搜索 files.autoSave,把"files.autoSave":"off" 修改成 "files.autoSave":"onFocusChange",意思是当编辑器失去焦点的时候就会... 查看详情

npmrun是什么?为什么使用npmrun这一命令,就能够将webpack跑起来并进行下一步的操作?(代码片段)

npmrun实际上是衔接node和webpack的连接点。先看看终端运行的npm是什么,如下图:图中的关键点是最后一行C:\\Users\\***\\AppData\\Roaming\\npm\\node_modules\\npm。从它可以推断出系统环境变量下配置的npm的路径为C:\\Users\\***\\AppData\\Roaming\\npm... 查看详情

flask:让第一个项目跑起来(代码片段)

...做哪些准备的朋友可以点击链接看看1、最简单的Flask项目代码:fromflaskimportFlask#创建一个应用app=Flask(__name__)@app.route("/")defindex():return"helloflask"if__name__==‘__main__‘:a 查看详情