在 Linux 系统下我们可以编写C语言程序并编译运行,我们就在本篇文章学习如何简单地编译运行C语言程序,并了解程序的翻译链接过程
使用gcc编译程序
首先我们要创建一个C语言文件,并编写代码
使用 vim 编写代码,以如下代码为例
使用gcc
命令编译我们刚才写的代码,会产生一个可执行程序,这个可执行程序可以任意命名
gcc test.c
如果不命名,就会产生一个名为a.out
的可执行文件
我们也可以将这个可执行程序命名
gcc -o mytest test.c
接着运行可执行程序,直接敲可执行程序的文件名,要说明文件所在目录,可以用绝对路径,也可以用相对路径
我们顺便也介绍一下c++程序的编译
编译C语言程序用的命令是gcc
,而c++用的是 g++
,因为c++兼容C语言,所以也可以用来编译C语言
c++文件以cpp
、cxx
、cc
结尾,这里我懒得打字,所以都用cc
结尾
如有以下代码
使用g++
命令编译文件
g++ -o mytestcpp test.cc
如果使用 g++
命令时出错,可能是没有安装,使用如下命令进行安装
sudo yum install -y gcc-c++
现在正常编译文件
运行可执行程序
程序的翻译过程
程序的翻译过程有:预处理、编译、汇编、链接
我们可以使用 gcc
命令和选项查看过程
过程 | gcc命令 | 命令的含义 | 过程说明 |
---|---|---|---|
预处理 | gcc -E test.c -o test.i | 从现在开始翻译 test.c 文件,预处理完成就停下来,输出的文件为test.i | 预处理阶段,完成头文件展开、宏替换、去掉注释、条件编译 |
编译 | gcc -S test.i -o test.s | 从现在开始翻译test.i 文件,编译完成就停下来,输出文件为test.s | 编译阶段,检查语法,将代码转为汇编代码 |
汇编 | gcc -c test.s -o test.o | 从现在开始翻译test.s 文件,汇编完成就停下,输出文件为test.o | 汇编阶段,将汇编代码编译为二进制目标文件 |
链接 | gcc test.o -o mytest.exe | 将test.o 与库等进行链接,输出可执行文件mytest.exe | 形成可执行程序 |
下面我们来详细看一下上述过程究竟是怎样的
预处理
我们就从下面这个代码来看看头文件展开、去注释、 宏替换
接着进行预处理
gcc -E test.c -o test.i
预处理完成后,我们查看test.i
文件
可以看到,源文件只有十几行,预处理后产生的 test.i
文件却有800多行。这就是头文件展开,实质就是将头文件的代码拷贝到我们的代码文件中
再来和源文件进行对比,宏M被替换为了 1 ,注释也被去掉了
下面再来看条件编译,有以下代码
进行预处理
查看proj.i
,可以看到,在没有定义V1
或V2
时,默认使用是else
下的代码
我们可以在编译时定义宏,使用gcc -D
就可以做到
执行可执行程序
也可以定义V2
编译
将test.i
文件进行编译
gcc -S test.i -o test.s
查看test.s
文件,确实被转为了汇编
汇编
将 test.s
文件进行汇编,输出目标文件
gcc -c test.s -o test.o
查看产生的目标文件
链接
将目标文件与库进行链接,最终输出可执行程序
gcc test.o
执行可执行程序
链接
链接是什么?
——链接就是将我们写的程序和库结合的过程
好,那么库又是什么呢?
一个语言,在编写的时候必定会有许多标准和方法,比如我们常用的printf()
,这时就会需要标准库来存放这些标准和方法,所以说一个语言一定要有自己的标准库。除了官方的标准库,还有许多第三方库可供我们使用。
在Linux下,我们可以使用如下命令查看一个可执行程序所链接的库
ldd 可执行程序名
有了标准库,我们也知道里面包含了许许多多的方法。那我们怎么知道有什么方法可以使用,又怎么使用呢?——这就需要头文件了,头文件就相当于库方法的说明书,我们可以根据头文件来调用库里的方法
动态链接与静态链接
库又分为动态库与静态库
在Linux下,动态库后缀为.so
,静态库后缀为.a
,虽然Linux不需要用后缀区分文件,但我们使用者需要
在Windows下,动态库后缀为.dll
,静态库后缀为.lib
既然库分为动态和静态,那么链接方式也就有动态链接与静态链接了
使用如下命令可以查看可执行程序的详细属性
file 文件名
以上可执行程序使用的是动态链接,使用共享库
那么动态链接和静态链接又是怎么样的呢?下面我们就来看一下
动态链接
在编译阶段, 程序还没运行的时候,编译器就会将动态库的地址告知给程序。当程序运行时,程序要执行库方法时,就会顺着动态库的地址寻找动态库,在其中执行库方法,然后返回到程序中,继续向下执行
动态链接的优缺点
- 依赖外部库。一旦动态库链接丢失或者动态库丢失,程序就运行不了了
- 节省资源。可执行程序中只有动态库的地址,用的时候直接跳转,不用开辟额外空间存储库;运行程序时,是在内存中运行,如果10个程序都用到了同一个方法,那么这个方法在内存中只要有一个即可,不用10个,即10个程序共用一个方法
静态链接
与动态链接不同的是,静态链接是在编译阶段将静态库本身本身拷贝到程序中,运行时直接在程序中调用,不需要用到库了
静态链接的优缺点
- 一旦形成,和库无关,库怎样都不会影响程序运行
- 浪费资源。将库拷贝到可执行程序中,那程序本身就很大了;运行10个静态链接的程序时,即使10个程序中都用到了同一个方法,内存中也会存在10相同个方法
综上,一般编译器都是默认提供的动态库,gcc默认形成的可执行程序,都是动态链接的。下面就来验证一下
动静态链接的验证
gcc在编译.c
文件时,默认是动态链接,我们可以使用如下指令来进行静态链接
gcc 源文件 -o 输出文件 -static
一般情况下,Linux是没有安装c/c++静态库的,可以使用如下指令安装
sudo yum install -y glibc-static libstdc++-static
现有test.c
文件,我们可以对这个文件分别动态链接和静态链接
gcc test.c -o mytest-static -static
gcc test.c -o mytest
然后我们就可以发现,静态链接后的可执行程序大小比动态链接的大太多了
我们也可以用ldd
指令查看这两个可执行程序
动态链接的程序有动态库,而静态链接的程序不能用ldd
查看
接着用file
来查看
也可以看到一个是动态链接,一个是静态链接
结束,再见 😄