gcc&g++

gcc&g++

1. 区别

​ gcc 最开始的时候是 GNU C Compiler, 如你所知,就是一个c编译器。但是后来因为这个项目里边集成了更多其他不同语言的编译器,GCC就代表the GNU Compiler Collection,所以表示一堆编译器的合集。 g++则是GCC里的c++编译器。

​ 现在你在编译代码时调用的gcc,已经不是当初那个c语言编译器了,更确切的说他是一个驱动程序,根据代码的后缀名来判断调用c编译器还是c++编译器 (g++)。比如你的代码后缀是*.c,他会调用c编译器还有linker去链接c的library。如果你的代码后缀是cpp, 他会调用g++编译器,当然library call也是c++版本的。

​ **gcc命令不能自动和c++程序使用的库联接,所以通常用g++来完成链接。**因此如果使用gcc来处理cpp文件,则需加一些参数:gcc helloworld.cpp -lstdc++ -o helloworld

​ 简单来说,把gcc当成c语言编译器,g++当成c++语言编译器用就是了。

2. 简单使用

对于GUN编译器来说,程序的编译要经历预处理编译汇编连接四个阶段。

-o:指定生成的输出文件;
-E:仅作预处理,不进行编译、汇编和链接;
-S:编译到汇编语言,不进行汇编和链接;
-c:编译、汇编到目标代码,不进行链接;
-wall:显示警告信息;
-I:用户设定的编译器头文件查找路径;
-L:用户设定的编译器库文件查找路径;
-l:指明编译器要链接哪些库;
0) 一步到位的编译指令
gcc test.c -o test
1) 预处理(Preprocessing)

在预处理阶段,输入的是C语言的源文件,通常为*.c。它们通常带有.h之类头文件的包含文件。这个阶段主要处理源文件中的#ifdef、 #include和#define命令。该阶段会生成一个中间文件*.i,但实际工作中通常不用专门生成这种文件。

预编译过程主要处理那些源代码中以#开始的预编译指令,主要处理规则如下:

  • 将所有的#define删除,并且展开所有的宏定义;处理所有条件编译指令,如#if,#ifdef等;
  • 处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件;
  • 删除所有的注释//和 /**/;
  • 添加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;
  • 保留所有的#pragma编译器指令,因为编译器须要使用它们;
gcc -E test.c -o test.i
2) 编译(Compilation)

在编译阶段,输入的是中间文件*.i,编译后生成汇编语言文件*.s

编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件。在这里GCC内真正起作用的是C编译器ccl。

gcc -S test.i -o test.s
3) 汇编 (Assembly)

在汇编阶段,将输入的汇编文件*.s转换成机器语言*.o。

汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。

gcc -c test.s -o test.o
4) 链接(Linking)

最后,在连接阶段将输入的机器代码文件 *.o 与其它的机器代码文件和库文件(附加的目标文件包括静态连接库和动态连接库)汇集成一个可执行的二进制代码文件。

链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件.在这个源程序中并没有定义printf的函数实现,且在预编译中包含进的"stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现printf函数的呢?最后的答案是:系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指定时,gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数printf了,而这也就是链接的作用。函数库一般分为静态库和动态库两种(后文介绍)。

gcc test.o -o test

3. 多源文件编译

1. 多个文件一起编译
用法:gcc testfun.c test.c -o test
作用:将testfun.c和test.c分别编译后链接成test可执行文件。
2. 分别编译再链接
gcc -c testfun.c //将testfun.c编译成testfun.o
gcc -c test.c   //将test.c编译成test.o
gcc -o testfun.o test.o -o test //将testfun.o和test.o链接成test

以上两种方法相比较,第一中方法编译时需要所有文件重新编译,而第二种方法可以只重新编译修改的文件,未修改的文件不用重新编译。

4. 检错

gcc -pedantic illcode.c -o illcode

-pedantic编译选项并不能保证被编译程序与ANSI/ISO C标准的完全兼容,它仅仅只能用来帮助Linux程序员离这个目标越来越近。或者换句话说,-pedantic选项能够帮助程序员发现一些不符合 ANSI/ISO C标准的代码,但不是全部,事实上只有ANSI/ISO C语言标准中要求进行编译器诊断的那些情况,才有可能被GCC发现并提出警告。

除了-pedantic之外,GCC还有一些其它编译选项也能够产生有用的警告信息。这些选项大多以-W开头,其中最有价值的当数-Wall了,使用它能够使GCC产生尽可能多的警告信息。

gcc -Wall illcode.c -o illcode

GCC给出的警告信息虽然从严格意义上说不能算作错误,但却很可能成为错误的栖身之所。一个优秀的Linux程序员应该尽量避免产生警告信息,使自己的代码始终保持标准、健壮的特性。所以将警告信息当成编码错误来对待,是一种值得赞扬的行为!所以,在编译程序时带上-Werror选项,那么GCC会在所有产生警告的地方停止编译,迫使程序员对自己的代码进行修改,如下:

gcc -Werror test.c -o test

5. 动态库与静态库

函数库一般分为静态库和动态库两种。

  • 静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为”.a”。
  • 动态库与之相反,在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,这样可以节省系统的开销。动态库一般后缀名为”.so”,如前面所述的libc.so.6就是动态库。gcc在编译时默认使用动态库。Linux下动态库文件的扩展名为".so"(Shared Object)。按照约定,所有动态库文件名的形式是libname.so(可能在名字中加入版本号)。这样,线程函数库被称作 libthread.so。静态库的文件名形式是libname.a。共享archive的文件名形式是libname.sa。共享archive只是一种过渡形式,帮助人们从静态库转变到动态库。)

先构造一个小的例子,用来说明如何用GCC编译得到动态库和静态库

hello1.c文件为:

void print1(int i) { 
	int j;
	for(j=0;j<i;j++) 
	{ 
		printf("%d * %d = %d\n",j,j,j*j); 
	}    
}

hello2.c文件为:

void print2(char *arr) { 
	char c; int i=0; 
	while((c=arr[i++])!='\0') 
	{ 
		printf("%d****%c\n",i,c); 
	}
}

hello.c文件为:

void print1(int); 
void print2(char *);
int main(int argc,char **argv) { 
  int i=100;
  char *arr="THIS IS LAYMU'S HOME!"; 
  print1(i); 
  print2(arr);
  return 0; 
}

hello.c使用到了hello1.c和hello2.c中的函数,可以把这两个函数组合为库,以供更多的程序作为组件来调用。下面采用编译为静态库和编译为动态库的方式分别进行说明。

5.1 静态库使用

  • 将hello1.c和hello2.c分别编译为hello1.o和hello2.o

    gcc -c hello1.c hello2.c
    
  • 将hello1.o和hello2.o组合为libhello.a这个静态链接库

    ar -r libhello.a hello1.o hello2.o
    
  • 将libhello.a拷贝到/usr/lib目录下,作为一个系统共享的静态链接库

    cp libhello.a /usr/lib
    
  • 将hello.c编译为可执行程序hello,这个过程用到了-lhello选项,这个选项告诉gcc编译器到/usr/lib目录下去找libhello.a的静态链接库

    gcc -o hello hello.c -lhello
    

执行生成的可执行程序hello,即可得到输出结果。开一下小的脑洞,既然hello中包含了libhello.a这个静态链接库文件的内容,如果删除libhello.a之后,那么hello依然也可以执行,结果的确是这个样子

5.2 动态库使用

  • 将hello1.c和hello2.c编译成hello1.o和hello2.o,-c意为只编译不链接,-fpic意为位置独立代码,指示编译程序生成的代码要适合共享库的内容这样的代码能够根据载入内存的位置计算内部地址。实际上只是告诉GCC产生的代码不要包含对函数和变量具体内存位置的引用,因为现在还无法知道使用该消息代码的应用程序会将它链接到哪一段内存地址空间。

    gcc -c -fpic hello1.c hello2.c
    
  • 将hello1.o和hello2.o组合为shared object,即动态链接库

    gcc -shared hello1.o hello2.o -o hello.so
    
  • 将hello.so拷贝到/usr/lib目录下

    cp hello.so /usr/lib
    
  • 将hello.c编译链接为hello的可执行程序,这个过程用到了动态链接库hello.so

    gcc -o hello hello.c hello.so
    

可以通过lld命令查看生成的hello程序以来的共享库包括哪些。

5.3 静态库与动态库链接、执行时的搜索路径顺序

5.3.1 静态库链接时搜索路径顺序
  1. ld会去找GCC命令行中的参数-L的目录中是否有该静态库;
  2. 再去找GCC的环境变量LIBRARY_PATH
  3. 再找内定目录/lib、/usr/lib、/usr/local/lib夏是否有该链接库,这是当初compile gcc的时候确定的
5.3.2 动态库链接时、执行时搜索路径顺序
  1. 编译目标代码时指定的动态库搜索路径;
  2. 环境变量LD_LIBRARY_PATH指定的动态库搜索路径;
  3. 配置文件/etc/ld.so.conf中指定的动态库搜索路径;
  4. 默认的动态搜索路径/lib;
  5. 默认的动态库搜索路径/usr/lib

大家可以去找找看相应的环境变量和conf文件

5.4 静态库与动态库的优缺点分析

静态链接库的一个缺点是,如果我们同时运行了很多程序,并且使用了同一个库函数,这样在内存中就会大量拷贝同一个库函数,这样会浪费很多内存和存储空间。

当一个程序使用动态库函数时,在链接阶段并不把函数代码链接进来,而只是链接函数的一个引用,当最终的函数倒入内存开始真正运行时,函数引用被解析,共享函数库的代码才真正被倒入到内存中,这样共享链接库的函数就可以被许多程序同时共享,而且只存储一次即可。同时动态库可以独立更新,与调用它的程序互不影响。

5.5 如何使用库

gcc中关于库的参数有:

-L 指定搜寻库的目录如指定当前目录 gcc -L .

-l 指定要链接的库的名称加入库的名称是libmylib.a,则gcc -l mylib,即去头去尾

–static 组织在链接时使用动态库

–shared 生成动态库

–static-libgcc 链接静态libgcc库

–shared-libgcc 链接动态libgcc库

更多博客文章,可访问我的新blog

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值