阳江 网站开发,厦门企业建站模板,松原企业网站建设,做个网站怎么做C程序编译过程及常见选项 一、编译过程1.预处理2.编译3.汇编4.链接#xff08;1#xff09;静态链接#xff08;1#xff09;动态链接 二、ELF可执行文件格式三、静态库和动态库1.静态库2.动态库3.制作静态库和动态库 四、GCC常见编译选项 了解gcc的编译流程#xff0c;我… C程序编译过程及常见选项 一、编译过程1.预处理2.编译3.汇编4.链接1静态链接1动态链接 二、ELF可执行文件格式三、静态库和动态库1.静态库2.动态库3.制作静态库和动态库 四、GCC常见编译选项 了解gcc的编译流程我们可以根据自己的需要让gcc在编译的任何阶段结束以便检查或使用编译器在该阶段的输出信息。或者对最后生成的二进制文件进行控制以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。 一、编译过程
//filenamehello.c
#include stdio.h
#define NUM 10 /* */int main (int argc, char **argv)
{int i;for(i 0; i NUM; i){printf(hello wolrd\n);}return 0;
}
从.c源文件到.out可执行文件可分为4个大步骤
预处理(Preprocessing)编译(Compilation)汇编(Assembly)链接(Linking)
1.预处理
预处理是读取c源文件对伪指令“替换”生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。但它仍是C文件内容有所改变。 伪指令包括 1宏定义指令如#define Name TokenString#undef以及编译器内建的一些宏如__DATE__, FILE, LINE, TIME, FUNCTION 等。 2条件编译指令如#ifdef#ifndef#else#elif#endif等。 3 头文件包含指令如#include FileName或者#include 等。 预处理的过程主要处理包括以下过程
将所有的#define删除并且展开所有的宏定义处理所有的条件预编译指令比如#if #ifdef #elif #else #endif等处理#include 预编译指令将被包含的文件插入到该预编译指令的位置。删除所有注释 “//”和”/* */”. 添加行号和文件标识以便编译时产生调试用的行号及编译错误警告行号。保留所有的#pragma编译器指令因为编译器需要使用它们
通常使用下面命令进行预处理参数-E表示进行预处理
gcc -E filename.c -o filename.i也可以使用以下指令完成预处理过程其中cpp是预处理器
cpp filename.c filename.i用cat和vim可查看.i文件 可看到原来的宏定义NUM已经替换成10而头文件也被替换了生成了一个700多行的源文件。
2.编译
编译程序所要作得工作就是通过词法分析和语法分析在确认所有的指令都符合语法规则之后将其翻译成等价的中间代码表 示或汇编代码。 使用下面命令进行编译生成汇编文件
gcc -S filename.i filename.s可以看到相比.i的预处理文件代码精简了很多只有50多行的代码。 我们使用PC的编译器gcc就会编译生成x86的汇编而使用ARM的编译器则生成ARM的汇编文件。同一份C代码不作任何修改使用不同的编译器编译就生成在不同机器上运行的程序这就是C程序的可移植性。我们在PC上编写程序在PC上用ARM的交叉编译器生成在ARM平台上的可执行程序的过程叫做交叉编译。
3.汇编
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序都将最终经过这 一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。 目标文件由段组成。通常一 个目标文件中至少有两个段
代码段(文本段)该段中所包含的主要是程序的指令。该段一般是可读和可执行的但一般却不可写数据段主要存放程序中要用到的各种常量、全局变量、静态的数据。一般数据段都是可读可写可执行的
gcc -c filename.s -o filename.o4.链接
汇编程序生成的目标文件并不能立即就被执行其中可能还有许多没有解决的问题。例如某个源文件中的函数可能引用了另一个源文件中定义的某个符号如变量或者函数调用等在程序中可能调用了某个库文件中的函数等等。所有的这些问题都需要经链接程序的处理方能得以解决。链接处理可分为两种 1.静态链接 2.动态链接
对于可执行文件中的函数调用可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小并且当共享对象被多个进程使用时能节约一些内存因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。
1静态链接
静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。在实际开发中我们都会将不同功能的代码放到不同的源文件而且它们存在一定的依赖关系但是每个源文件是独立编译的每个.c文件会生成.o文件为了满足依赖关系就需要对这些目标文件进行链接从而形成一个可执行文件。这个过程就是静态链接。 静态链接的优缺点 造成空间浪费静态链接库的时候是以目标文件为单位如果多个程序对同一个函数有调用那么就对同一个目标文件有所依赖例如多个程序调用printf()函数那么可执行文件中就存在多个printf.o的副本。若库文件修改了就要对程序进行重新编译。静态链接的优点是可执行文件中具备程序运行的所有需要的文件执行速度毋庸置疑就快了。 静态链接的过程
1动态链接
可解决静态链接的两个问题1是空间浪费2是更新困难。 动态链接把链接这个过程推迟到了运行时再进行在可执行文件装载时或运行时由操作系统的装载程序加载库。动态链接的基本思想是把程序按照模块拆分成各个相对独立部分在程序运行时才将它们链接在一起形成一个完整的程序而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。 动态链接的优缺点 优点是多个程序共用一个.o文件副本便以更新自动链接新版本的目标文件。缺点是把链接推迟到程序运行时每次执行程序都需要链接因此性能会损失。
du (disk usage): 显示每个文件和目录的磁盘使用空间也就是文件的大小。
命令参数
-k 、 -m #显示目录中文件的大小-k 单位KB-m 单位MB
-h #以K M G为单位显示提高可读性最常用的一个
--max-depth1 #显示层级
du -h --max-depth1同一个hello.o文件分别用静态和动态的方法链接通过du 命令查看文件的大小可以看到动态链接的文件比静态链接的文件小很多。
二、ELF可执行文件格式
ELF文件由4部分组成分别是ELF头ELF header、程序头表Program header table、节Section和节头表Section header table。 Linux/Unix的可执行文件以及动态库都是以ELF(Executable Linkage Format)存在的。在Linux下可以使用readelf命令查看ELF文件关于加载过程所需要的信息都在ELF文件头里面可以用使readefl filename -e来查看EFL文件头部的信息。我们可以先来查看下hello.c编译出来的hello可执行文件的ELF头信息 readelf -d filename(可执行文件)显示动态节区的内容 readelf -a filename可执行文件,打印ELF详细信息可查看函数入口地址 数据段和文本段的地址是在链接的时候决定的而栈是动态的。
ldd命令可用来查看动态链接可执行文件依赖于哪些动态库文件 linux-vdso.so.1是Linux的装载器其解析ELF文件信息并加载到相应的地址空间上去
三、静态库和动态库
库是一种可以执行代码的二进制形式可被操作系统载入内存执行。调用别人的库时我们需要遵守许可协议使用库可以为我们节省大量的时间提高开发效率。Linux下库文件分两种静态库和动态库均有.o文件生产。
1.静态库
静态库文件名的命名方式是“libxxx.a”,库名前加”lib”windows和linux下都是后缀用”.a”“xxx”为静态库名windows下的静态库名也叫libxxx.a链接时间 静态库的代码是在编译过程中被载入程序中。链接方式静态库的链接是将整个函数库的所有数据都整合进了目标代码。这样做优点是在编译后的执行程序不在需要外部的函数库支持因为所使用的函数都已经被编进去了。缺点是可执行文件占用内存空间较大且如果所使用的静态库发生更新改变你的程序必须重新编译。
2.动态库
动态库的命名方式与静态库类似前缀相同为“lib”linux下后缀名为“.so(sharedobject)”即libxxx.so而windows 下后缀名为“.dll(dynamic linklibrary)”即libxxx.dll链接时间动态库在编译的时候并没有被编译进目标代码而是当你的程序执行到相关函数时才调用该函数库里的相应函数。这样做缺点是因为函数库并没有整合进程序所以程序的运行环境必须提供相应的库。优点是动态库的改变并不影响你的程序所以动态函数库升级比较方便。
它们两个还有很明显的不同点当同一个程序分别使用静态库动态库两种方式生成两个可执行文件时静态链接所生成的文件所占用的内存要远远大于动态链接所生成的文件。这是因为静态链接是在编译时将所有的函数都编译进了程序而动态链接是在程序运行时由操作系统帮忙把动态库调入到内存空间中使用。另外如果动态库和静态库同时存在时链接器优先使用动态库。
3.制作静态库和动态库
例如我们希望把file1.c、file2.c、…fileN.c做成库文件。 静态库制作需将各.c文件逐个编译成.o文件再打包生产库文件。
gcc -c file1.c
gcc -c file2.c ...
gcc -c fileN.c
ar -rcs libname.a file1.o file2.o ... fileN.o动态库制作
gcc -shared -fPIC -o libname.so file1.c file2.c ... fileN.c使用静态库或动态库 gcc main.c -o myapp -L lib_path -lname-L 就是要告诉编译链接器把库文件链接进来-lname的name要去掉库文件前缀和后缀。 示例将pf.c先转成目标文件pf.o再转成库文件libpf.amain.c文件调用了在pf.c定义的func函数所以main.c编译成可执行文件的时候 需要用到头文件pf.h以及库文件libpf.a
pf.c文件
#include stdio.hint func(void)
{printf(hello wolrd!\n);return 0;
}
pf.h文件
#ifndef _PF_H_
#define _PF_H_
/* this function used to print hello world! */
extern int func(void);#endif /* ----- #ifndef _PF_H_ ----- */
制作静态库libpf.a过程如下 制作动态库libpf.so过程如下 其中头文件解决编译的问题而静态库解决链接的问题动态库解决链接和运行时的问题。具体使用步骤如下 main.c文件
#include stdio.h
#include pf.h //表示在当前目录下寻找头文件当前目录找不到时请系统库文件找int main (int argc, char **argv)
{func();//调用了在pf.c里面定义的函数printf(func() execute successfully!\n);return 0;
} main.c程序编译 我们可以看到main.c编译时在当前路径下找不到pf.h文件因为我们的pf.h文件定义在了ch10/library下。 gcc在编译的时候找不到头文件。这时我们可以使用编译器的 -I大写i选项来指定头文件的路径 gcc在链接的时候找不到函数的定义即库文件。这时要告诉链接器在链接的时候要链接包含这些函数的库文件通过-l (小写l) 选项来指 定: 这时候你一定很惊讶为什么指定了库文件的名字却找不到它原因是链接器默认到系统动态库路径/lib、/lib64、/usr/lib下查找相应的库文件如果找不到就出错。链接的时候抛出错误去链接库找原因。如果使用的动态库不在这些路径下我们就可以使用-L大写l选项来指定相应库的路径 从上面的结果可以看出如果动态库和静态库同时存在则优先使用动态库链接。而如果想使用静态库链接一种方法是把在/ch10/library下的动态库移除另外一种方法是在编译时加上链接选项 -static当然这样程序中所有的库都使用静态链接库了。 程序运行 静态编译的程序因为所有代码段和数据段都被链接进可执行程序中所以可以直接运行 而动态编译的程序动态库中的代码段和数据段并没有被链接进可执行程序中只是记录了需要他们的一些信息。所以程序在运行时和生成可执行文件一样需要操作系统帮忙加载这些动态库程序。这样直接执行就会出错 Linux下在运行程序时会默认到 /lib、/lib64、/usr/lib以LD_LIBRARY_PATH环境变量指定的路径下查找所需的动态库下查找所需的动态库文件如果没有则抛错。而这时libmycrypto.so并不在系统库路径下所以会出错。这时有两种解决方法
将所需要的libmycrypto.so文件拷贝到/usr/lib路径下当然这需要root权限使用export命令在LD_LIBRARY_PATH环境变量中添加该动态库所在的路径注意该命令只是临时生效重启后失效。另 外指定的路径必须是绝对路径
四、GCC常见编译选项
gcc的编译选项非常非常多我们可以使用man手册查看下面是我们经常使用的一些编译选项
选项 说明
-E 只进行预处理不编译
-S 只编译不汇编
-c 只编译、汇编不链接
-g 编译生成可执行文件包含gdb调试信息可被gdb调试
-o 指定编译生成可执行文件名
-I 指定include包含文件的搜索目录
-L 指定链接所需库(动态库或静态库)所在路径
-l 指定所需链接库的库名
-ansi ANSI标准
-stdc99 C99标准
-Werror 不区分警告和错误遇到任何警告都停止编译
-Wall 开启大部分警告提示
--static 静态编译
-static 静态链接
-O0 关闭所有优化选项
-O1 第一级别优化使用此选项可使可执行文件更小、运行更快并不会增加太多编译时间可以简写
为-O
-O2 第二级别优化采用了几乎所有的优化技术使用此选项会延长编译时间
-O3 第三级别优化在-O2的基础上增加了产生inline函数、使用寄存器等优化技术
-Os 此选项类似于-O2作用是优化所占用的空间但不会进行性能优化常用于生成最终版本