C语言 | 【耗费一夜总结三本C语言系列】之 编译步骤 会用C还不知道C如何编译

前言

本章内容简单介绍了编译器,并讲述了编译器的过程。

在这里插入图片描述


C语言 | 快速了解C的发展史🧡💛💚💙
C语言 | 【耗费一夜总结三本C语言系列】之 指针、数组 一文透彻~~~🧡💛💚💙
C语言 | 【耗费一夜总结三本C语言系列】之 结构体、联合、枚举🧡💛💚💙
C语言 | 【耗费一夜总结三本C语言系列】之 声明🧡💛💚💙
C语言 | 【耗费一夜总结三本C语言系列】之 作用域 在也不用担心分不清变量的作用域拉!!!🧡💛💚💙
C语言 | 【耗费一夜总结三本C语言系列】之 编译步骤 会用C还不知道C如何编译???🧡💛💚💙
C语言 | 【耗费一夜总结三本C语言系列】之 数据类型总结🧡💛💚💙
C语言 | 【耗费一夜总结三本C语言系列】之 位及进制的用法🧡💛💚💙


一、为何需要编译

我们现在所学的编程语言(除汇编语言)都属高级编程语言,以多种方式简化了我们的编程工作。如使用c语言,代码不想汇编那样繁琐。接触了python,会觉得编程变得更加简便。高级语言使用高级的指令让程序员更使用更加贴切更抽象得去描述问题,或者你的想法

1.1 编译器的作用到底是什么呢?

编译器简介

编译器不是一个单一的程序,它分别由预处理器、语法、语义检查器、代码生成器、汇编程序、优化器、链接器、等组成,由编译器驱动器来调控;

int a = 10;
int b = a;

当我们写出代码时,这些代码仅仅是我们能够看懂的指令。而非计算机所能识别的指令,对于计算机而言,它只认识机器语言。要如何才能够让他们转换成相应的语言呢?此时编译器就充当中间者的身份。将程序员编写的高级语言翻译成计算机能理解的机器语言,从而让代码能够在计算机上运行。

且编译器在不同的CPU产商中使用的指令系统编码格式不同,需要合适的编译器便可将高级语言转换给不同类型的cpu识别

  • 编译器由编译器设计者所决定,在不同的编译器所采取的行动可能并不相同。
  • 编译器只有在违反语法规则约束条件下才会产生错误。例如:你的代码出现问题,它可能会中止你从程序、或者发出一条警告、或者啥也不干。

1.2 编译器限制来保证了代码的可移植性

ANSI C规定每个ANSI C编译器必须要支持以下几点【举例,若要详细需查看文档】:

  • 函数定义形参数量不能少于31个;
  • 函数调用时实参数量不能少于31个;
  • 一个条源代码行至少可以由509个字符;
  • 表达式中至少可以支持32层嵌套括号

1.3 不同系统使用的编译器不同

  • Windows 下常用的是微软开发的 Visual C++,它被集成在 Visual Studio 中,一般不单独使用;
  • Linux 下常用的是 GUN 组织开发的 GCC,很多 Linux 发行版都自带 GCC
  • Mac 下常用的是 LLVM/Clang,它被集成在 Xcode 中(Xcode 以前集成的是 GCC,后来由于 GCC 的不配合才改为 LLVM/Clang,LLVM/Clang 的性能比 GCC 更加强大)。

二、编译步骤

典型的C语言实质上通过编译和链接俩个步骤来完成。其中编译器将.c文件装成中间代码,在通过链接将代码合并,从而生成可执行文件。

可知,C在编译大型项目时,速度会很慢,甚至需要几个小时。如果编译好后,需要调整,则需要再次编译。针对于此,C采用多个模块化,将头文件写在.h中,源代码写在.c中,且通过不同的功能在对文件进行细分。可对其进行单独编译,在使用链接器合并。如若其中一个文件有过修改,则只需单独将更改过的模块重新编译即可,节省了大量的时间成本,提高文件修改调试效率。

在这里插入图片描述

2.1 编译

步骤

  • 预处理;
  • 编译;
  • 目标文件。
2.1.1 预处理【宏定义、文件包含、条件编译】
  • 头文件展开,并将头文件的源代码插入;
    • 各种声明;
    • inline函数的定义;
    • 宏定义;
    • 全局变量定义;
    • 外部变量定义;
  • 宏展开也就是将宏定义与代码中使用的宏替换,并且删除#define;
  • 处理条件编译指令;
  • 删除源代码中的注释,并且添加行号以及文件名标识
  • 执行#pragma定义的相关指令;

在这里插入图片描述

使用预处理命令g++ -o test.i -E test.cpp即可生成一个预处理后的文件,多达2万多行。

在这里插入图片描述

2.1.2 编译

此过程编译器将会检查代码的规范性,以及语法的正确性,以及确定了代码要做的实际工作。若无误,则生成汇编代码。

使用命令g++ -S test.i -o test.s即可生成汇编代码。

在这里插入图片描述

2.1.3 目标文件

汇编文件转换成二进制文件,虽然此时包含机器语言但依旧不能直接运行。因为该代码只是存储编译器翻译的源代码,并不是一个完整的程序;缺失启动代码以及库代码

启动代码

启动代码根据不同的系统或者硬件系统而不同,充当着程序与操作系统之间的接口

库代码

存储正在的函数代码。

使用g++ –c test.s –o test.o文件

在这里插入图片描述

2.2 链接

目标文件不能直接执行,还需要载入连接器。链接器会确认main函数的进入点,再将目标文件库代码、系统的标准启动代码进行链接,对于库代码只需将程序中所用的函数提取即可。且将源代码中的库代码与目标形成计算机可识别的二进制机器代码即可执行程序。

使用g++ test.o –o test.exe即可生成test.exe的可执行文件;

在这里插入图片描述

2.2.1 静态链接

即函数库(仅所需要的函数)的一份拷贝是可执行文件的物理组成部分;

  • .a为后缀;
  • 易出现与升级后的系统不兼容的情况;
2.2.2 动态链接

即可执行文件包含文件名,并且在载入器运行时(将函数库映射到进程中)可找到所需的函数库;

  • .so为后缀;

优点

  • 可执行文件的体积小
  • 运行速度稍慢,但节省磁盘空间虚拟内存
  • 能让程序与使用的特定的函数库版本分离,供应用程序二进制接口(介于程序与函数库二进制间服务),便于函数库版本升级,无需重新链接;
  • 允许运行时选择需要执行的函数库;

2.3 一步到位

g++ test.cpp -o test即可生成test可执行文件。

2.4 运行

程序运行时,载入器会找到main的入口,并且在此之前,载入器会把共享的数据对象载入到进程的地址空间。外部函数不调用,则载入器不会对其进行解析,即不会造成额外的开销;

当程序要运行时,会被分为三个阶段分别为:

  • 链接-编辑;
  • 载入;
    运行时链接;
2.4.1 链接编辑
  • 在静态链接下,该模块被链接编辑后即运行;
  • 在动态链接下,该模块被链接编辑后,又在运行时链接即运行;

3. 静态与动态库的创建及使用

3.1 静态库

命名规则

  • 【lib库的名称.a】

制作

.c文件生成.o文件打包
生成命令:ar rcs 静态库名称 生成的.o文件

使用

打包头文件以及静态库即可;

  • 使用命令:gcc main.c lib/静态库 -o a.out -I头文件路径
  • 或者:gcc main.c -Iinclude -L 静态库的目录 -l 库的名称 -o a.out

优点

  • 生成程序的时候,不需要提供对应的库,只需要相应的.o文件;
  • 加载库的速度快;

缺点

  • 库被打包到应用程序,导致库的体积很大;
  • 库发生了改变,需要重新编译环境;
3.2 共享库

命名规则

  • 【lib名称.so】

制作步骤

  • 生成与位置无关的代码;
  • .o打包成共享库(动态库);

制作

  • 创建与位置无关的.o文件 gcc -fPIC -c *.c -I../include
  • gcc -shared lib命名.so

使用

当生成的可执行文件出现找不到该共享库时:

  • 可使用ldd 可执行文件查看动态链接器;
    • 动态链接器是根据环境变量进行查找;
  • 若找不到可将so文件放到lib中(不推荐);
  • 或者加入export LD_LIBRARY_PATH=...
  • 或者修改/etc/ld.so.conf添加动态库的路径,在使用ldconfig执行(推荐);

优点

  • 执行程序体积小;
  • 动态库更新不需要重新编译程序;

缺点

  • 需要提供动态库;
  • 速度相对慢,没有打包到应用程序中;

请添加图片描述

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jxiepc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值