Linux开发工具有:编译器gcc 调试器gdb工程管理器make以及脚本语言shell
这篇我主要浅谈一下编译器gcc的功能以及相关的使用方法,其他开发工具会依次的另写博客进行详谈。
提到Linux我们首先得提到开源社区的相关协议。
GPL协议:在开源社区的原码,修改后也必须开源(其它内容请自主查询)
POSIX标准:软件既可以在UNIX上运行,也能在Linux上运行
GNU计划(GNU is not UNIX):只能在Linux上运行,UNIX不可以
GNU CC(gcc):是GNU中符合ANSI C标准的编译系统
gcc是交叉编译器 ,交叉编译器是在一个平台(CPU平台)下编译成另一个平台下运行的代码(不同的CPU有不同的汇编,不同的汇编生成不同的机器码(每一款处理器即使是同系列的都有自己的汇编语言))
以hello.c举例:
gcc hello.c
file a.out
arm-linux-gcc hello.c -o hello
file hello
可看出两者的区别:前者为X86系列 后者已经编译为arm平台的代码
在linux里面用gcc编译的程序时,编译的过程可以细分为四个阶段:
(1)预处理(Pre-Processing)
(2)编译(Compiling)
(3)汇编(Assembling)
(4)链接(Linking)
1.预处理:头文件加载 宏替换 条件编译
gcc -E test.c -o test.i
编译预处理主要是读取C源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。
伪指令主要包括以下四个方面:
(1)宏定义指令,如# define Name TokenString,#undef等。对于前一个伪指令,预编译所要作得的是将程序中的所有Name用TokenString替换,但作为字符串常量的Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。
(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif,等等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。
(3)头文件包含指令,如#include "FileName"或者#include <FileName>等。在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号(<>)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。
(4)特殊符号,预编译程序可以识别一些特殊的符号。例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。 预编译程序所完成的基本上是对源程序的“替代”工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件。
2.编译阶段:语法检查 词法分析
gcc - c test.c :在编译阶段停止
3.汇编阶段:把目标文件转换为汇编文件
gcc -c test.s -o test.o
4.链接阶段
“gcc test.o -o test”,运行完成后就会产生一个名为test的可执行文件。
链接程序 由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例 如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调 用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链 接程序的处理方能得以解决。 链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号 同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。
通常文件编译时都会有警告,而我们编写C程序的目标是消除警告,而有的警告是隐藏的,直接gcc hello.c并不会显示出来,这就要求我们打开所有警告
此命令为:gcc -Wall hello.c
有打开所有警告当然也会有关闭警告
此命令为:gcc -w hello.c (一般情况不允许关闭)
库的使用在gcc上也非常重要,库函数的特点就是只能调用,不能看实现;库文件的特点是:不能看到库文件里的定义的函数的变量但是可以使用。
库一般分为两类:静态库(.a)和动态库(.so)
静态库:在编译时将库文件里的代码搬迁到执行文件里
动态库:在执行时将需要的了库文件里的代码搬迁到可执行文件里
静态库与动态库的区别:
静态库:可执行文件代码体积大 执行效率高 不易于升级(需要重新编译) 编译速度慢 代码布局更好
动态库:可执行文件代码体积小 执行效率低 易于升级 编译速度快 代码布局难
(根据运行环境判断使用哪个库)
但是如何将程序制作成库呢?下面我们以main.c add.c(将add.c制作成库在main.c中使用)为例。
静态库:
gcc -c add.c
ar rcs libadd.a add.o
gcc main.c -ladd -L/root/(所在目录)或者gcc main.c -ladd -L.
(-L:制定库文件搜索路径)
动态库:
gcc -shared -fPIC add.c -o libadd.so
gcc main.c ./libadd.so -o add或者gcc main.c /root/(文件所在目录)/libadd.so
库函数在:
根目录 lib中:系统运行依赖的库
usr/lib:程序软件运行依赖的库