一、gcc与g++
gcc是GNU项目中符合ANSI C标准的编译系统。
g++是将 gcc 默认语言设为 C++ 的一个特殊的版本,链接时它自动使用 C++ 标准库而不用 C 标准库。
注意事项
GCC和G++两者都可以编译C和C++代码,但应该请注意:
(1)后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序。
(2)编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接。
二、选项
(一)文件后缀
.c C 源程序;预处理,编译,汇编
.C .cxx .cp .cpp .c++ C++源文件
.m Objective-C 源程序;预处理,编译,汇编
.mi 预处理后的objective-C源文件
.i 预处理后的C 文件;编译,汇编
.ii 预处理后的 C++文件;编译,汇编
.s 汇编语言源程序文件
.S 必须预处理的汇编程序文件
.h 预处理器文件;通常不出现在命令行上
.o 目标文件
.a 静态链接库
.so 动态链接库
(二)C语言编译过程
GCC编译程序时,编译过程分为四个阶段:预处理、编译、汇编、和连接
1、预处理阶段
对其中以#开头的指令(伪指令)和特殊符号进行处理。
(1)预处理程序将以#include行所指的文件代替该指令(包含的文件可能是宏定义、各种外部符号的声明、及另外的头文件)
#include "FILE.h" 与 #include <FILE.h> 的区别:前者先在当前目录中搜索头文件,找不到再到/usr/include目录下寻找该头文件。后者只在/usr/include目录下寻找该头文件。
(2)预处理程序对C语言源程序中的所有宏名进行宏替换。
(3)预处理程序对条件编译指令(#ifdef #ifndef #else #elif #endif)将根据有关的条件将某些代码滤掉,使之不进行编译。
(4)预处理程序对源程序中出现的_LINE_,_FILE_等预先定义好的宏名,将用合适的值进行替换:对#line #error #pragma分别进行预处理
2、编译阶段
对预处理后的输出文件进行词法分析和语法分析,试图找出所有不符合语法规则的部分。在确定各成分符合语法规则后将其翻译成功能等价的中间代码表示或者汇编代码。
3、汇编过程
把汇编语言代码翻译成机器代码的过程
4、连接阶段
将文件中引用的符号(如变量或函数调用)与该符号在另外一个文件中的定义连接起来,从而使有关的目标文件连成一个整体,最终成为可执行文件。
静态连接:是在编译时把函数的代码从其所有的静态链接库或归档文件中被复制到可执行文件中,从在程序执行之前已经练成一个完整的代码。
动态链接:将函数的代码放在动态链接库或共享对象的某个目标文件中,在最终的可执行文件中只是记录下共享对象的名字及其他少量相关信息,在执行时才把函数代码从动态链接库中找出,连入可执行文件中。
(三)GCC命令行选项
1、预处理选项
-C 在预处理后的输出中保留源文件中的注释
-E 只对指定的源文件进行预处理,不做编译,生成的结果送到标准输出
-D name 定义一个宏,而且其值为1
-D name=definition 定义一个宏,并指定其值为definition。作用等价于在源文件中使用宏定义指令
-U name 取消先前对宏name 的任何定义,不管是内置的还是由-D选项提供的
-I dir 指定搜索头文件的路径dir,先在指定路径中搜索要包含的头文件,若找不到则在(/usr/include,/usr/lib及当前工作目录)上搜索
-o file 将输出写到file指定的文件中
2、编译程序选项
-c 只生成目标文件,不进行连接。用于对源文件的分别编译
-S 只进行编译不进行汇编,生成汇编代码文件格式,其名与源文件相同但扩展名为.s
-g 指示编译程序在目标代码中加入供调试程序gdb使用的附加信息
-v 在标准出错输出上显示编译阶段所执行的命令,即编译驱动程序及预处理程序的版本号
-o file 将输出放在文件file中,若未用该选项则放在a.out中
3、优化程序选项
-O0 不执行优化
-O -O1试图减少代码大小和执行时间,但不执行优化
-O2 在O1级的优化之上还进行额外的调整工作(除不做循环展开、函数内联和寄存器重命名外几乎所有的优化)
-O3 完成O2之外的优化
-Os 具有-O2级别的优化,同时并不增加代码
4、连接程序选项
-o file 指定连接程序最后生成的可执行文件名称为file
-B prefix 选项规定在什么地方查找可执行文件、库文件、包含文件和编译程序本身数据文件
-L dir 把指定的目录dir加到连接程序搜索库文件的路径表中(首先在dir下搜索,找不到再到标准位置下搜索)
-static 在支持动态连接的系统中,强制使用静态连接
-l library 连接时搜索由library命名的库
5、错误信息选项
-pedantic 使用了ANSI/ISO C语言扩展语法的地方将产生相应的警告信息
-Wall 产生尽可能多的警告信息,建议始终带上
-Werror 将所有的警告当成错误进行处理
4、编译多个源文件
多个源文件编译有以下两种方式:
1. 多个文件一起编译
用法: $gcc hello.c hello_fn.c -o test
作用:将hello.c和hello_fn.c 分别编译后链接成test可执行文件。编译时需要所有文件重新编译
2.分别编译各个源文件,之后对编译后输出的目标文件链接。可以只重新编译修改的文件,未修改的文件不用重新编译。
用法:
$ gcc -c hello.c //将hello.c 编译成hello.o
$ gcc -c hello_fn.c //将hello_fn.c编译成hello_fn.o
$ gcc -o hello.o hello_fn.o -o test //将hello.o和hello_fn.o链接成test
将hello world分割成三个文件,
//‘hello.c’主程序,包含头文件,调用hello函数
#include"hello.h"
int main(void)
{
hello ("world");
return 0;
}
//hello_fn.c 定义 hello 函数
#include<stdio.h>
#include"hello.h"
void hello (constchar *name)
{
printf("hell,%s\n",name);
}
//hello.h 头文件
void hello (constchar * name);
编译:
~$ cd/home/yufei/mytest/
A: $ gcc -Wall hello.c hello_fn.c -onewhello
$ ./newhello
B: $ gcc -Wall -c hello.c
$ gcc -Wall -c hello_fn.c
$ gcc -o hello.o hello_fn.o -o test
5、创建静态库
静态库是编译器生成的普通的.o 文件的集合。链接一个程序时用库中的对象文件还是目录中的对象文件都是一样的。静态库的另一个名字叫归档文件(archive),管理这种归档文件的工具叫 ar 。
创建静态库的步骤:
1、创建并编译库中所需的各个模块
2、用 ar将编译好的对象文件添加进库中
例:
首先要编译出库中需要的对象模块。下面的两个源码文件
//helloa
#include<stdio.h>
void helloa ()
{
printf("this ishelloa\n");
}
//hellob
#include<stdio.h>
void hellob ()
{
printf("this ishellob\n");
}
将源码文件编译成对象文件:
$ gcc -c -Wallhelloa.c hellob.c
程序 ar 可以将对象文件插入库中。(如果库不存在的话,参数 -r 将创建一个新的,并将对象模块添加到归档文件中)。
创建一个包含本例中两个对象模块的名为libhello.a 的静态库:
$ ar -r libhello.ahelloa.o hellob.o
ar: creatinglibhello.a
现在库已经构建完成可以使用了。下面的程序twohell.c 将调用该库中的这两个函数:
//twohell
void helloa(void);
void hellob(void);
int main (int argc,char *argv[])
{
helloa();
hellob();
return 0 ;
}
程序 twohellos可以通过在命令行中指定库用一条命令来编译和链接,命令如下:
$ gcc -Wall twohell.clibhello.a -o twohellos
$ ./twohellos
this is helloa
this is hellob
静态库的命名惯例是名字以三个字母lib 开头并以後缀 .a 结束。所有的系统库都采用这种命名惯例,并且它允许通过 -l(ell) 选项来简写命令行中的库名。下面的命令与先前命令的区别仅在于gcc 期望的找寻该库的位置不同:
$ gcc -Wall twohell.c-llibhello -o twohellos
指定完整的路径名可使编译器在给定的目录中寻找库。库名可以指定为绝对路径(比如/usr/worklibs/libhello.a)或者相对与当前目录的路径(比如 ./lib/libhello.a)。选项 -l不能具有指定路径的能力,但是它要求编译器在系统库目录下找寻该库。
6、创建共享库
共享库是编译器以一种特殊的方式生成的对象文件的集合。对象文件模块中所有地址(变量引用或函数调用)都是相对而不是绝对的,这使得共享模块可以在程序的运行过程中被动态地调用和执行。
构建共享库的步骤:
1、创建并将库中所需的模块编译为对象文件
$ gcc -c -Wall-fpic xxx.c xxxx.c
选项 -c 告诉编译器只生成 .o的对象文件。选项 -fpic 使生成的对象模块采用浮动的(可重定位的)地址。 pic “位置无关代码”(position independent code)。
2、将对象文件构建成一个共享库
$ gcc -Wall -shared xxx.o xxxx.o -o xxxxx.so
文件后缀 .so 告诉编译器将对象文件链接成一个共享库。通常,链接器定位并使用 main()函数作为程序的入口,但是本例中输出模块中没有这种入口点,为抑制错误选项 -shared 是必须的。
也可以直接将模块编译并存储为共享库($ gcc -Wall -fpic -shared xxx.c xxxx.c -o xxxxx.so)
例:
//sharea
#include<stdio.h>
void sharea ()
{
printf("The isfirst hello from sharedlibrary\n");
}
//shareb
#include<stdio.h>
void shareb ()
{
printf("The issecond hello from sharedlibrary\n");
}
将以上两个源码文件编译成对象文件:
$ gcc -c -Wall -fpicsharea.c shareb.c
将对象文件构建成一个名为hello.so 的共享库:
$ gcc -Wall -sharedsharea.o shareb.o -o hello.so
也可以直接将模块编译并存储为共享库
$ gcc -Wall -fpicsharea.c shareb.c -o hello.so
下面的 twoshare.c 是调用共享库中两个函数的主程序:
//twoshare
void sharea(void);
void shareb(void);
int main (int argc,char *argv[])
{
sharea();
shareb();
return 0 ;
}
该程序可以用下面的命令编译并链接共享库:
$ gcc -Walltwoshare.c /home/yufei/mytest/hello.so -o twoshare
$ ./twoshare
The is first hellofrom shared library
The is second hellofrom shared library