在Linux环境下,我们通常用gcc将C代码编译成可执行文件,如下就是一个简单的例子:

小实验:hello.c

#include <stdlib.h>
#include <stdio.h>
void main(void)
{
printf("hello world!\r\n");
}

可以通过如下指令来编译出一个可执行文件:

gcc hello.c

执行完该命令后,就会得到一个a.out的可执行文件。

编译的过程

前面的例子只是简单的介绍了一下gcc的使用方法,熟悉c编程的朋友就会知道,该步骤其实包含了预处理–>编译–>汇编–>链接四步,这四步分别实现的功能如下:

  • 预处理阶段:主要处理源文件中的#ifdef、 #include和#define命令,展开宏、读取定义的符号等(.i)
  • 编译阶段:检查代码的规范性,把代码翻译成汇编语言(.s)
  • 汇编阶段:是把编译阶段生成的文件转成二进制目标代码(.o)
  • 链接阶段:将汇编阶段生成的机器码汇集成一个可执行的二进制代码文件

由此可以看出,每一个阶段的输出其实就是下一个阶段的输入,用gcc是可以单独执行这四步的:

gcc -E hello.c -o hello.i
gcc -S hello.i -o hello.s
gcc -c hello.s -o hello.o
gcc hello.o -o hello

实际上,由于这四个步骤太过于复杂,往往可以像我上面那样全部集中到一个命令中来执行:

gcc hello.c -o hello

这里我加了一个-o参数来指定输出名称,而不是默认的a.out。

-o 优化选项, 这个选项不是标准的

  • -O和 -O1指定1级优化
  • -O2 指定2级优化
  • -O3 指定3级优化
  • -O0指定不优化

    gcc hello.c -O3 -O0 hello 当出现多个优化时,以最后一个为准!!

如果有多个文件,则可以通过如下方式全部集中起来。

gcc -o test first.c second.c third.c

这个全生成的方式虽然非常简单,但是存在的一个问题就是:当项目较大时,如果只改了一个文件,仍需要重新编译索引文件。

为了解决这个问题,我们往往把这个编译过程拆分成两步:

  • 将各个.c文件分别编译成.o文件
  • 将所有.o文件链接成执行文件

    gcc –c first.c

    gcc –c second.c

    gcc –c third.c

    gcc -o test first.o second.o third.o

这样,当third.c文件发生改变时,只需要重新编译third.c和链接即可,这样就省去了未变化文件的编译时间,也就是我们通常所说的增量编译。

gcc –c third.c
gcc -o test first.o second.o third.o

从上面的使用方法中我们也可以看到:

  • 当使用-c参数时,若输入文件时.c则会同时执行执行了预处理、编译、汇编三个阶段,直接生成.o文件。
  • 输入文件为.o时,可以直接执行链接操作

由于程序员往往并不关心前面两个几个阶段生成的输出文件,通常我们也把预处理、编译、汇编三个阶段合并在一起,统称为编译,输入.c,生成.o。

常用参数:

前面其实已经演示过-E、–S、–c、–o等几个参数的用法,其中-E及-S很少会用到,-c用于编译生成.o文件,-o用于指定输出文件名称。除了这几个生成控制的参数外,还有许多参数设置,这里主要介绍一下几个常用的:

包含头文件和库:

  • -Idir :指定编译查找头文件的目录,常用于查找第三方的库的头文件,例:gcc test.c –I../inc -o test。
  • -Ldir :指定链接时查找lib的目录,常用于查找第三方库。
  • -llibrary :指定额外链接的lib库

宏定义:

  • -DMACRO :以字符串”1”(默认值)定义 MACRO 宏。
  • -DMACRO=DEFN :以字符串”DEFN”定义MACRO 宏,注意中间不能有空格。
  • -UMACRO :取消对 MACRO 宏的定义。

调试和可执行文件形式:

  • -g :指示编译器,在编译的时产生调试信息。
  • -ggdb :尽可能的生成gdb的可以使用的调试信息(比-g生成的信息更多些)。
  • -static :禁止使用动态库,编译得到的程序会比较大,但可以自由运行。
  • -share :尽量使用动态库,所以生成文件比较小,但是需要系统由动态库。

告警选项:

  • -Wall :产生尽可能多的警告信息,建议始终带上
  • -Werror :将所有的警告当成错误进行处理

gcc和g++

除了gcc编译器外,还有另外一个编译器g++,很多人往往搞不清楚这两个编译器的区别,很多人望文生义的认为gcc只能编c代码,g++只能编c++代码。实际上这两个编译器的主要区别如下:

  1. 后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序来编译,后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。C++的语法规则更加严谨一些。
  2. 链接的时候gcc不会默认加上-lstdc++选项,而g++会,所以导致gcc编译c++代码时,用到了stl库时会出现链接失败。
  3. gcc不会定义__cplusplus宏,而g++会,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。

静态链接库

有时我们需要将一组代码编成一个库,从而方便其复用。例如,我们调用的STL和系统函数都是以这种方式提供的。另外,当项目工程较大时,为了使其模块化方便分工,有时也需要将其创建自己的链接库。

代码演示:

// stack.c
#include <stdio.h>
char stack[512];
int top = -1;
char pop(void){
return stack[top--];
}
void push(char c){
stack[++top] = c;
}

要把stack.c编成lib,需要经过如下两个步骤:

  1. 通过gcc –c命令将stack.c编成stack.o gcc -c stack.c
  2. 通过ar命令将stack.o封装成libstack.a ar cr libstack.a stack.o

创建测试文件

// main.c
#include <stdio.h>
char pop();
void push(char c);
void main(void){
push('a');
push('b');
printf("%c\n", pop());
}

链接编译:

gcc -o run main.c -L. -lstack

上述过程中,用到了条之前没见过的命令ar。ar是archive的缩写,也就是归档的意思,平时我们用得更多的是另一条归档命令tar。ar和tar的功能其实比较类似,但ar命令做了一些额外的处理,它会为被归档的目标文件中的符号建立索引,当和应用程序链接时,建立的这些索引将回收链接过程

ar命令的参数比较多,如果只是创建lib库的话,通常只用到了cr这两个组合参数。该命令是可以接受多个输入文件,统一合并到一个库中。

ar cr libtest.a first.o second.o third.o

在通过ar创建lib后,可以通过ar -t命令查看该lib里打包了那些.o文件

ar -t libstack.a
stack.o

此外,还可以通过nm命令来查看符号表等更多信息

nm libstack.a
stack.o:
0000000000000000 T pop
0000000000000021 T push
0000000000000200 C stack
0000000000000000 D top

动态链接库

传统方式下,库函数的链接是在编译器完成的,所有相关对象在编译的时候被整合成一个可执行文件。与此相比,我们也可以把对库函数的链接载入推迟到程序运行的时期,也就是我们所称作的动态链接。

动态链接的优点

除了静态链接库所有的模块化和代码复用外,动态链接库还有如下优点:

  1. 可以实现进程之间的库共享:当多个进程共享一个库时(如stl库和一些系统库是基本上大多数程序都用的),动态链接方式可以只在内存中保留一份副本,节约内存。
  2. 升级变得简单:用户只需要升级动态链接库,而无需重新编译链接其他原有的代码就可以完成整个程序的升级(很多Windows的补丁就是这种方式发布的)。
  3. 可以动态载入:当软件比较大的时候,可以根据需要动态载入/卸载相应的链接库,而无需像静态链接的方式那样必须一次性全部载入

创建动态链接库的方式比较简单,还是按静态链接库的例子,我们只需要通过gcc -shared指令即可创建一个libstack.so的动态库(静态库一般以.a作为扩展名,动态库一般以.so作为扩展名)。

gcc -c -fpic stack.c
gcc -shared -o libstack.so stack.o

这里必须带上-fpic,使输出的对象模块是按照可重定位地址方式生成的。

在链接阶段使用动态库的方式基本上和静态库一致。

gcc -o run main.c -L. -lstack

编译玩这个程序后,我们执行后却发现,它报动态链接库找不到的错误提示。

./run
./run: error while loading shared libraries: libstack.so: cannot open shared object file: No such file or directory

我们也可以通过ldd命令查看某程序当前对动态链接库的依赖情况:

ldd run
linux-vdso.so.1 => (0x00007fff366fe000)
libstack.so => not found
libc.so.6 => /lib64/libc.so.6 (0x00002ba53ad1f000)
/lib64/ld-linux-x86-64.so.2 (0x00002ba53ac03000)

ldd的结果表明了我们生成的libstack.so找不到。因为动态链接库是一个可以共享的文件,因此往往存放在一个公共的位置,在Linux系统中程序查找动态链接库的规则如下:

  • 首先在环境变量LD_LIBRARY_PATH所记录的路径中查找。
  • 然后从缓存文件/etc/ld.so.cache中查找。
  • 如果上述步骤都找不到,则到默认的系统路径中查找,先是/lib然后是/usr/lib。

很明显,这几个路径都不包含当前路径。要解决上述问题,一个简单的方式就是把当前路径加到环境变量中:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

然后再次用ldd名称测试,发现现在就能找到我们的链接库了。

ldd run
linux-vdso.so.1 => (0x00007fff581ff000)
libstack.so (0x00002ad148cf0000)
libc.so.6 => /lib64/libc.so.6 (0x00002ad148e0b000)
/lib64/ld-linux-x86-64.so.2 (0x00002ad148bd4000)

不过,很多大牛并不建议通过修改LD_LIBRARY_PATH这种方式

LD_LIBRARY_PATH is not the answer http://prefetch.net/articles/linkers.badldlibrary.html
Why LD_LIBRARY_PATH is bad http://xahlee.org/UnixResource_dir/_/ldpath.html
LD_LIBRARY_PATH - just say no http://blogs.sun.com/rie/date/20040710

Linux高级编程--02.gcc和动态库的更多相关文章

  1. linux c编程调用系统的动态库时,要使用dlopen等函数吗?

    同问 linux c编程调用系统的动态库时,要使用dlopen等函数吗? 2012-11-27 21:55 提问者: hnwlxyzhl 我来帮他解答 满意回答 2012-12-07 09:08 li ...

  2. Linux gcc链接动态库出错:LIBRARY_PATH和LD_LIBRARY_PATH的区别

    昨天在自己的CentOs7.1上写makefile的时候,发现在一个C程序在编译并链接一个已生成好的lib动态库的时候出错.链接命令大概是这样的: [root@typecodes tcpmsg]# g ...

  3. gcc 编译动态库和静态库

    Linux C 编程入门之一:gcc 编译动态库和静态库 cheungmine 2012 参考: C程序编译过程浅析 http://blog.csdn.net/koudaidai/article/de ...

  4. 如何使用GCC生成动态库和静态库

    根据链接时期的不同,库又有静态库和动态库之分.静态库是在链接阶段被链接的,所以生成的可执行文件就不受库的影响,即使库被删除,程序依然可以成功运行.而动态库是在程序执行的时候被链接的.程序执行完,库仍需 ...

  5. linux高级编程基础系列:线程间通信

    linux高级编程基础系列:线程间通信 转载:原文地址http://blog.163.com/jimking_2010/blog/static/1716015352013102510748824/ 线 ...

  6. 【C++】如何使用GCC生成动态库和静态库

    一.静态库和动态库的定义及区别 程序编译的四个过程: 1.预处理  展开头文件/宏替换/去掉注释/条件编译(.i后缀) 2.编译    检查语法,生成汇编(.s后缀) 3.汇编    汇编代码转换成机 ...

  7. Linux gcc编译(动态库,静态库)

    1. linux 库路径: /lib , /usr/lib , /usr/local/lib 2.linux 编译静态库 a.编写源文件vi pr1.c void print1(){    print ...

  8. linux 高级编程之库的使用

    一.静态库与动态库 静态库: .a .lib 动态库: .so .dll 差别(静态库中的代码在链接时就已经复制到可执行文件中,执行时不再依赖库,不会自动使用升级后的库,需要重新产生可执行文件. 动态 ...

  9. gcc中动态库和静态库的链接顺序

    so文件:动态库a文件: 静态库exe文件:可执行程序(linux下以文件属性来标示是否是可执行文件,与后缀名无关) 经过自己写的一些测试程序,大致了解了下gcc中链接顺序问题,总结出以下几点:1,动 ...

随机推荐

  1. Nginx 配置站点

    1-进入 配置文件夹   cd /etc/nginx/cof.d   2-创建一个一站点名称命名的配置文件   vim kestrel-syslyracom.conf   3-在.conf 文件中输入 ...

  2. lintcode-24-LFU缓存

    24-LFU缓存 LFU是一个著名的缓存算法 实现LFU中的set 和 get 样例 capacity = 3 set(2,2) set(1,1) get(2) >> 2 get(1) & ...

  3. 使用ssh公钥登陆

    记录一下使用的具体命令,具体参考: Centos设置禁止密码登录而只使用密钥登录SSH方法  优先参考这个. ssh使用公钥授权不通过的问题解决 Xshell配置ssh免密码登录-密钥公钥(Publi ...

  4. Win2019 显示 SMBV1 协议不安全的处理

    1. 登录有问题. 报错 [Window Title] \\10.100.1.163 [Content] \\10.100.1.163 因为文件共享不安全,所以你不能连接到文件共享.此共享需要过时的 ...

  5. 第163天:js面向对象-对象创建方式总结

    面向对象-对象创建方式总结 1. 创建对象的方式,json方式 推荐使用的场合: 作为函数的参数,临时只用一次的场景.比如设置函数原型对象. var obj = {}; //对象有自己的 属性 和 行 ...

  6. 第80天:jQuery插件使用

    jQuery其他补充+ 4.1 链式编程: end()补充 * 补充五角星 评论案例 * 第一步:鼠标移入,当前五角星和前面的五角星变实体.后面的变空心五角星 * 第二步:鼠标点击的时候,为当前元素添 ...

  7. HttpWebRequest和HttpWebResponse的应用

    创建使用类HttpHelper: public class Httpparam { public string UserAgent { get; set; } public string Accept ...

  8. ZOJ3591_Nim

    题目的意思是给你n个ai,有多少种不同的连续段使得用该段数中所有的数字玩Nim游戏的先手必胜. 首先根据博弈论的知识,我们知道,要使先手必胜,那么只要保证所有的数的异或值不为0就可以了. 这个题目,给 ...

  9. CF708C-Centroids

    题目 一棵树的重心定义为一个点满足删除这个点后最大的连通块大小小于等于原来这颗树大小的一半. 给出一棵树,一次操作为删除一条边再添加一条边,操作结束后必须仍为一棵树.问这颗树的每个点是否可以通过一次操 ...

  10. 【数据库_Postgresql】sql语句添加序号,timestamp格式时间截取日期和时间

    SELECT ROW_NUMBER() OVER (ORDER BY sr.receiptid ASC) AS 序号, sr.receiptid, sr.receiptdate, DATE(sr.re ...