gcc详解

  • Post author:
  • Post category:其他


一、GCC

GNU编译器套件(GNU Compiler Collection)包括

C



C++



Objective-C



Fortran



Java



Ada



Go语言

的前端,也包括了这些语言的库(如libstdc++、libgcj等等)。GCC的初衷是为GNU操作系统专门编写的一款编译器。GNU系统是彻底的

自由软件

。此处,“自由”的含义是它尊重用户的自由。[1]



1

创作背景


编辑




GCC(GNU Compiler Collection,GNU编译器套件),是由 GNU 开发的编程语言编译器。它是以

GPL

许可证所发行的自由软件,也是 GNU计划的关键部分。GCC原本作为GNU操作系统的官方编译器,现已被大多数类

Unix

操作系统(如

Linux



BSD



Mac OS X

等)采纳为标准的

编译器

,GCC同样适用于微软的

Windows

。[2]


GCC是自由软件过程发展中的著名例子,由自由软件基金会以

GPL协议

发布。
GCC 原名为 GNU C 语言

编译器

(GNU C Compiler)[3]


,因为它原本只能处理

C语言

。GCC 很快地扩展,变得可处理

C++

。后来又扩展能够支持更多编程语言,如

Fortran



Pascal



Objective-C



Java



Ada



Go

以及各类

处理器架构

上的

汇编语言

等,所以改名GNU编译器套件(GNU Compiler Collection)。[2]


2

结构


编辑




GCC的外部接口长得像一个标准的Unix

编译器

。使用者在命令列下键入gcc之程序名,以及一些命令参数,以便决定每个输入档案使用的个别语言

编译器

,并为输出程序码使用适合此硬件平台的组合语言

编译器

,并且选择性地执行

连接器

以制造可执行的程序。
每个语言

编译器

都是独立程序,此程序可处理输入的原始码,并输出组合语言码。全部的语言

编译器

都拥有共通的中介架构:一个前端解析符合此语言的原始码,并产生一

抽象语法树

,以及一翻译此语法树成为GCC的

暂存器

转换语言〈RTL〉的后端。

编译器

最佳化与静态程序码解析技术(例如FORTIFY_SOURCE,一个试图发现

缓冲区

溢位〈buffer overflow〉的编译器)在此阶段应用于程序码上。最后,适用于此硬件架构的组合语言程序码以Jack Davidson与Chris Fraser发明的算法产出。
几乎全部的GCC都由C写成,除了Ada前端大部分以Ada写成。








前端接口

前端的功能在于产生一个可让后端处理之语法树。此语法解析器是手写之

递归

语法解析器。
直到2004年,程序的语法树结构尚无法与欲产出的处理器架构脱钩。而语法树的规则有时在不同的语言前端也不一样,有些前端会提供它们特别的语法树规则。
在2005年,两种与语言脱钩的新型态语法树纳入GCC中。它们称为GENERIC与GIMPLE。语法解析变成产生与语言相关的暂时

语法树

,再将它们转成GENERIC。之后再使用”gimplifier”技术降低GENERIC的复杂结构,成为一较简单的静态唯一形式(Static Single Assignment form,SSA)基础的GIMPLE形式。此形式是一个与语言和处理器架构脱钩的全域最佳化通用语言,适用于大多数的现代编程语言。








中介接口

一般

编译器

作者会将语法树的最佳化放在前端,但其实此步骤并不看语言的种类而有不同,且不需要用到语法解析器。因此GCC作者们将此步骤归入通称为中介阶段的部分里。此类的最佳化包括消解死码、消解重复运算与全域数值重编码等。许多最佳化技巧也正在实作中。








后端接口

GCC后端的行为因不同的前处理器



和特定架构的功能而不同,例如不同的字符尺寸、呼叫方式与大小尾序等。后端接口的前半部利用这些讯息决定其RTL的生成形式,因此虽然GCC的RTL理论上不受

处理器

影响,但在此阶段其抽象指令已被转换成目标架构的格式。
GCC的最佳化技巧依其释出版本而有很大不同,但都包含了标准的最佳化算法,例如循环最佳化、执行绪跳跃、共通程序子句消减、指令排程等等。而RTL的最佳化由于可用的情形较少,且缺乏较高阶的资讯,因此相比较起来,增加的GIMPLE语法树形式,便显得比较不重要。
后端经由一次重读取步骤后,利用描述目标处理器的

指令集

时所取得的信息,将抽象

暂存器

替换成

处理器

的真实暂存器。此阶段非常复杂,因为它必须关注所有GCC可移植平台的处理器

指令集

的规格与技术细节。
后端的最后步骤相当公式化,仅仅将前一阶段得到的

汇编语言

代码藉由简单的

子例程

转换其

暂存器

与内存位置成相对应的

机器码


3

基本用法


编辑




在使用GCC

编译器

的时候,我们必须给出一系列必要的调用参数和文件名称。GCC

编译器

的调用参数大约有100多个,这里只介绍其中最基本、最常用的参数。具体可参考GCC Manual。
GCC最基本的用法是∶gcc [options] [filenames]
其中options就是

编译器

所需要的参数,filenames给出相关的文件名称。
-c,只

编译

,不链接成为

可执行文件



编译器

只是由输入的.c等

源代码

文件生成.o为后缀的目标文件,通常用于编译不包含主程序的

子程序

文件。
-o output_filename,确定输出文件的名称为output_filename,同时这个名称不能和源文件同名。如果不给出这个选项,gcc就给出预设的

可执行文件

a.out。
-g,产生符号调试工具(GNU的gdb)所必要的符号资讯,要想对

源代码

进行调试,我们就必须加入这个选项。
-O,对程序进行优化

编译

、链接,采用这个选项,整个

源代码

会在编译、链接过程中进行优化处理,这样产生的

可执行文件

的执行效率可以提高,但是,编译、链接的速度就相应地要慢一些。
-O2,比-O更好的优化

编译

、链接,当然整个编译、链接过程会更慢。
-Idirname,将dirname所指出的目录加入到程序头文件目录列表中,是在

预编译

过程中使用的参数。C程序中的头文件包含两种情况∶
A)#include <myinc.h>
B)#include “myinc.h”
其中,A类使用尖括号(< >),B类使用双引号(“ ”)。对于A类,

预处理

程序cpp在系统预设包含

文件目录

(如/usr/include)中搜寻相应的文件,而B类,预处理程序在

目标文件

的文件夹内搜索相应文件。
-v gcc执行时执行的详细过程,gcc及其相关程序的版本号
原版gcc manual该选项英文解释
Print (on standard error output) the commands executed to run the stages of compilation. Also print the version number of the compiler driver program and of the preprocessor and the compiler proper.
编译程序时加上该选项可以看到gcc搜索头文件/库文件时使用的搜索路径!


GCC编译器的选项解读


1. 基本选项


-E是只进行预处理选项,不进行编译、汇编、以及连接


-S 编译后停止,不进行会变和连接


-c编译或会汇编文件,但不进行连接


-o file 指定输出文件名


2. 警告选项


-Wall 启用所有警告信息


-Werror 在发生警告时取消编译操作,即将警报看作是错误


-w 禁用所有警告信息


3. 优化选项


-O0:不进行优化处理


-O或-O1:进行基本的优化,这些优化在大所属情况下都会使程序执行的更快


-O2:除了完成-O1级别的优化外,还需要一些其他的调整工作,如处理器指令调度等,只是GNU发布软件的默认优化


-O3:除了完成-O2级别的优化外,还进行循环的展开(这往往会提高执行速度)以及其他的一些预处理器相关的优化工作。


-Os:生成最小的可执行文件,主要用在嵌入式领域。


4. 连接器选项


-Idirectory 向GCC的头文件搜索路径中添加新的目录


-Ldirectory 向GCC的库文件搜索路径中添加一个行的目录


-llibrary 提示连接程序在创建可执行文件时包含指定的库文件


-static 强制使用静态库


-shared 强制使用共享库


5. 其他选项


-xlanguage 指定输入文件的编程语言


-v 显示编译器的版本号


-g 获得有关调试程序的详细信息


-ansi 支持符合ansi彼岸准的c程序


4

基本规则


编辑




gcc所遵循的部分约定规则:
.c为后缀的文件,C语言

源代码

文件;
.a为后缀的文件,是由

目标文件

构成的档案库文件;
.C,.cc或.cxx 为后缀的文件,是C++

源代码

文件且必须要经过

预处理

.h为后缀的文件,是程序所包含的

头文件

.i 为后缀的文件,是C

源代码

文件且不应该对其执行

预处理

.ii为后缀的文件,是C++

源代码

文件且不应该对其执行

预处理

.m为后缀的文件,是Objective-C

源代码

文件;
.mm为后缀的文件是Objective-C++

源代码

文件;
.o为后缀的文件,是

编译

后的

目标文件

.s为后缀的文件,是

汇编

语言

源代码

文件;
.S为后缀的文件,是经过

预编译



汇编

语言

源代码

文件。


5

语言支持


编辑




以2006年5月24日释出的4.1.1版为准,本

编译器

版本可处理下列语言:
Ada 〈GNAT〉
C 〈GCC〉

C++

(G++)

Fortran

〈Fortran 77: G77, Fortran 90: GFORTRAN〉

Java



编译器

:GCJ;

解释器

:GIJ〉

Objective-C

〈GOBJC〉
Objective-C++
先前版本纳入的CHILL前端由于缺乏维护而被废弃。
Fortran前端在4.0版之前是G77,此前端仅支援Fortran 77。在本版本中,G77被废弃而采用更新的GFortran,因为此前端支援Fortran 95。
下列前端依然存在:
Modula-2
Modula-3
PL/I
Mercury


6

执行过程


编辑




虽然我们称GCC是

C语言



编译器

,但使用gcc由C语言

源代码

文件生成

可执行文件

的过程不仅仅是编译的过程,而是要经历四个相互关联的步骤∶

预处理

(也称

预编译

,Preprocessing)、

编译

(Compilation)、

汇编

(Assembly)和链接(Linking)。
命令gcc首先调用cpp进行

预处理

,在预处理过程中,对

源代码

文件中的文件包含(include)、

预编译

语句(如



定义define等)进行分析。接着调用cc1进行

编译

,这个阶段根据输入文件生成以.i为后缀的目标文件。

汇编

过程是针对汇编语言的步骤,调用as进行工作,一般来讲,.S为后缀的汇编语言

源代码

文件和汇编、.s为后缀的汇编语言文件经过

预编译



汇编

之后都生成以.o为后缀的目标文件。当所有的

目标文件

都生成之后,gcc就调用ld来完成最后的关键性工作,这个阶段就是连接。在连接阶段,所有的

目标文件

被安排在可执行程序中的恰当的位置,同时,该程序所调用到的

库函数

也从各自所在的档案库中连到合适的地方。


7

执行过程示例


编辑





  • 示例代码
1
2
3
4
5
6
7

#include<stdio.h>

int


main(


void


)

{




printf


(


"hello\n"


);



return


0;

}

这个过程处理

宏定义

和include,并做语法检查。
可以看到

预编译

后,代码从6行扩展到了910行。
1
2
3
4
5

gcc


-E a.c -o a.i

cat


a.c|


wc


-l

5

cat


a.i|


wc


-l

910


  • 编译过程
这个阶段,生成

汇编

代码。
1
2
3

gcc


-S a.i -o a.s

cat


a.s|


wc


-l

59

这个阶段,生成

目标代码

此过程生成ELF格式的

目标代码

1
2
3

gcc


-c a.s -o a.o

file


a.o

a.o:ELF64-bitLSBrelocatable,AMDx86-64,version1(SYSV),notstripped

链接过程。生成

可执行代码

。链接分为两种,一种是

静态链接

,另外一种是

动态链接

。使用

静态链接

的好处是,依赖的

动态链接

库较少,对动态链接库的版本不会很敏感,具有较好的兼容性;缺点是生成的程序比较大。使用

动态链接

的好处是,生成的程序比较小,占用较少的内存。
1

gcc


a.o -o a

程序运行:
1
2

.


/a

hello


8

处理器架构


编辑




GCC4.1支持下列处理器架构:
Alpha
Atmel AVR
Blackfin
H8/300
IA-32〈x86〉 与

x86-64
MorphoSys 家族
Motorola 88000
System/370,System/390
SuperH
HC12
VAX
Renesas R8C/M16C/M32C家族
较不知名的处理器架构也在官方释出版本中支援:
A29K
ARC
C4x
CRIS
D30V
DSP16xx
FR-30
FR-V
Intel i960
IP2000
M32R
68HC11
MCORE
MN10200
MN10300
NS32K
ROMP
Stormy16
V850
Xtensa
由FSF个别维护的GCC处理器架构:
D10V
MicroBlaze
PDP-10
MSP430
Z8000
当GCC需要移植到一个新平台上,通常使用此平台固有的语言来撰写其初始阶段。


9

程序除错


编辑




为 GCC 除错的首选工具当然是 GNU 除错器。其他特殊用途的除错工具是 Valgrind, 用以发现内存漏失 (Memory leak)。而 GNU 测量器 (

gprof

) 可以得知程序中某些函式花费多少时间,以及其呼叫频率;此功能需要使用者在

编译

时选定测量〈profiling〉选项。


10

使用技巧


编辑




首先检查是否在你的机器上安装了GCC,使用命令:
可用rpm -q gcc 检查。
如果没有安装,请依序检查并安装下面各RPM
libbinutils
binutils
make
glibc-devel
gcc-cpp
gcc
看下面的例子:test.c
1
2
3
4
5
6
7
8

#include<stdio.h>

int


main()

{




char


*str=


"I like Linux!I advices you join in the Linux World"


;



printf


(


"%s"


,str);



return


0;

}

使用gcc

编译

。输入gcc -c test.c得到目标文件test.o.-c命令表示对文件进行编译和

汇编

。但并不连接。如果再键入gcc -o ../bin/test test.o,那么将得到名为test的

可执行文件

。其实这两步可以一气呵成,gcc ../bin/test test.c.如果程序没有错误就生成了

可执行文件

。也许你会觉得基于命令行的

编译器

比不上如VC之类的

集成开发环境

,的确gcc的界面要改进,但是你一旦熟练了就会感到。gcc的效率如此之高。可以告诉大家的是Linux底下强大的C/C++

集成开发环境


Kdevelop

和Vc一样强大,使用了Gcc

编译器

GNU C

编译

器 即gcc是一个功能强大的

ANSI C

兼容编译器,你会操作其他

操作系统

下的一种C编译器,能很快掌握GCC.
1、使用Gcc,Gcc是基于命令行的,使用时通常后跟一些选项和文件名。Gcc的基本用法如下: gcc [options] [filenames] 命令行选项制定操作将对命令行上的每个给出的文件执行。
2、GCC的常用选项

编译

选项:gcc有超过100个的编译选项可用。具体的可以使用命令man gcc察看
优化选项:用GCC

编译

C/C++代码时,它会试着用最少的时间完成编译并且编译后的代码易于调试。易于调试意味着

编译

后的代码与

源代码

有同样的执行顺序,编译后的代码没有经过优化。有很多的选项可以告诉GCC在耗费更多

编译时间

和牺牲易调试性的基础上产生更小更快的

可执行文件

。这些选项中最典型的就是-O和-O2。-O选项告诉gcc对

源代码

进行基本优化。-O2选项告诉GCC产生尽可能小的和尽可能快的代码。还有一些很特殊的选项可以通过man gcc察看。
调试和剖析选项:GCC支持数种调试剖析选项。在这些选项中最常用的是-g和-pg.-g选项告诉gcc产生能被GNU调试器(如gdb)使用的调试信息,以便调试用户的程序。-pg选项告诉gcc在用户的程序中加入额外的代码,执行时,产生

gprof

用的剖析信息以显示程序的耗时情况。
3、使用gdb
使用方法:在命令行中键入gdb并按回车就可以运行gdb了,启动gdb后,能在命令行上制定很多的选项,也可以下面的方式来运行gdb: gdb filename 用这种方式运行gdb时,能直接指定想要调试的程序。在命令行上健入gdb -h得到一个有关gdb的选项的说明简单列表。

编译

代码以供调试,为了使gdb工作,必须使程序在编译时包含调试信息,调试信息包含程序里的每个

变量

的类型,在

可执行文件

里的

地址映射

以及

源代码



行号

。gdb利用这些信息使

源代码



机器码

相关联。
关于gcc的大体就写这么多吧,更多的信息可以查找帮助,记得学习Linux的一大武器man或者info命令,下次在介绍一下使用C/C++编写大型程序的

makefile

文件和make命令。


11

版本发布


编辑




2012年03月23日,GCC 首个公开发布版本是在 1987 年由 Richard Stallman 发布的,到今天已经整整 25 年了。为了庆祝 25 周年,GCC 也相应发布了 GCC 4.7.0 版本,这是 GCC 一个全新的重要版本。
GCC 4.7.0 带来了一组关于链接时优化 (LTO) 框架可提升伸缩性和降低内存使用,据开发者称,在 64 位系统上需要 8G 内存来对 Firefox 进行优化,而是用了 LTO 后只需 3G。
此外就是体验的支持软件事务内存,支持更多 C++11 标准,包括原子性、C++11 内存模型,用户定义文字、别名声明、构造器委派和可扩展的语法等。
GCC 4.7.0 还改进对

Fortran

的支持,支持 Google Go 1 等等多项改进。[4]

2012年06月14日,GCC 4.7.1 发布了,这是一个 bug 修复版本,主要是 4.7.0 中的一些回归测试发现的问题修复。[5]

2013年04月11日,GCC 4.7.3 发布。
2013年03月22日,GCC 4.8.0发布,进一步加强了对已C++11的支持。并且G++开始支持-std=c++1y选项,用来支持计划于2014年发布的C++11修订版标准(C++14)。[6]

2013年10月16日,GCC 4.8.2发布。提供了对于OpenMP 4.0的支持。
2014年04月22日,GCC发布了4.9.0版本,提供了对

C11

标准的Generic Selection语法特性(_Generic)的支持以及对

多线程

方面特性的支持。


12

linux中安装


编辑











RedHat中安装

用which gcc命令查看,假如有显示” /usr/bin/gcc”的话说明已经安装了,否则就是没有安装。
这里以redhat 64位 linux为例。首先将redhat的iso系统镜像挂在到/mnt/cdrom目录下:


mount -o loop /data/rhel-server-5.4-x86_64-dvd.iso /mnt/cdrom


进入/mnt/cdrom目录,就可以访问iso镜像中的内容了。


cd /mnt/cdrom


cd Server/
安装gcc的依赖包以及gcc,按以下命令依次执行:


rpm -ivh binutils-2.17.50.0.6-12.el5.x86_64.rpm


rpm -ivh cpp-4.1.2-46.el5.x86_64.rpm


rpm -ivh kernel-headers-2.6.18-164.el5.x86_64.rpm


rpm -ivh glibc-devel-2.5-42.x86_64.rpm


rpm -ivh glibc-headers-2.5-42.x86_64.rpm


rpm -ivh libgomp-4.4.0-6.el5.x86_64.rpm


rpm -ivh gcc-4.1.2-46.el5.x86_64.rpm


大家在安装rpm包时,由于依赖关系,在安装时会提示“此包依赖其他包xx”,让你先安装其他包,总之大家按照提示来,提示你先安装哪个包就安装哪个包。








Ubuntu中安装

安装4.8版为例:
sudo apt-get install gcc-4.8


gcc和g++的区别

http://www.linuxsky.org/doc/dev/200804/298_2.html

gcc和g++都是GNU(组织)的一个编译器。

误区一:gcc只能编译c代码,g++只能编译c++代码


两者都可以,但是请注意:


1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。C++的语法规则更加严谨一些。


2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。




误区二:gcc不会定义__cplusplus宏,而g++会


实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。




误区三:编译只能用gcc,链接只能用g++



严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用

g++或者gcc -lstdc++

。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。



我们在编译c/c++代码的时候,有人用gcc,有人用g++,于是各种说法都来了,譬如c代码用gcc,而c++代码用g++,或者说编译用gcc,链接用g++,一时也不知哪个说法正确,如果再遇上个extern “C”,分歧就更多了,这里我想作个了结,毕竟知识的目的是令人更清醒,而不是更糊涂。




误区一:gcc只能编译c代码,g++只能编译c++代码

两者都可以,但是请注意:


1.

后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的

,例如:


#include <stdio.h>


int main(int argc, char* argv[]) {



if(argv == 0) return;


printString(argv);


return;


}


int printString(char* string) {



sprintf(string, “This is a test.n”);


}


如果按照C的语法规则,OK,没问题,但是,一旦把后缀改为cpp,立刻报三个错:“printString未定义”;


“cannot convert `char**\’ to `char*”;


”return-statement with no value“;

分别对应前面红色标注的部分。可见C++的语法规则更加严谨一些。


2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。




误区二:gcc不会定义__cplusplus宏,而g++会

实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。




误区三:编译只能用gcc,链接只能用g++

严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。




误区四:extern “C”与gcc/g++有关系

实际上并无关系,无论是gcc还是g++,用extern “c”时,都是以C的命名方式来为symbol命名,否则,都以c++方式命名。试验如下:


me.h:


extern “C” void CppPrintf(void);




me.cpp:


#include <iostream>


#include “me.h”


using namespace std;


void CppPrintf(void)


{



cout << “Hellon”;


}




test.cpp:


#include <stdlib.h>


#include <stdio.h>


#include “me.h”


int main(void)


{



CppPrintf();


return 0;


}




1. 先给me.h加上extern “C”,看用gcc和g++命名有什么不同

[root@root G++]# g++ -S me.cpp


[root@root G++]# less me.s


.globl _Z9CppPrintfv        //注意此函数的命名


.type   CppPrintf, @function


[root@root GCC]# gcc -S me.cpp


[root@root GCC]# less me.s


.globl _Z9CppPrintfv        //注意此函数的命名


.type   CppPrintf, @function

完全相同!




2. 去掉me.h中extern “C”,看用gcc和g++命名有什么不同

[root@root GCC]# gcc -S me.cpp


[root@root GCC]# less me.s


.globl _Z9CppPrintfv        //注意此函数的命名


.type   _Z9CppPrintfv, @function


[root@root G++]# g++ -S me.cpp


[root@root G++]# less me.s


.globl _Z9CppPrintfv        //注意此函数的命名


.type   _Z9CppPrintfv, @function


完全相同!




.h头文件 .lib库文件 .dll动态库文件之间的关系



分类:

C语言



.h头文件是编译时必须的,lib是链接时需要的,dll是运行时需要的。


附加依赖项的是.lib不是.dll,若生成了DLL,则肯定也生成 LIB文件。如果要完成源代码的编译和链接,有头文件和lib就够了。如果也使动态连接的程序运行起来,有dll就够了。在开发和调试阶段,当然最好都有。


.h .lib .dll三者的关系是:


H文件作用是:声明函数接口


DLL文件作用是: 函数可执行代码


当我们在自己的程序中引用了一个H文件里的函数,编链器怎么知道该调用哪个DLL文件呢?这就是LIB文件的作用: 告诉链接器 调用的函数在哪个DLL中,函数执行代码在DLL中的什么位置,这也就是为什么需要附加依赖项 .LIB文件,它起到桥梁的作用。如果生成静态库文件,则没有DLL ,只有lib,这时函数可执行代码部分也在lib文件中


目前以lib后缀的库有两种,一种为静态链接库(Static Libary,以下简称“静态库”),另一种为动态连接库(DLL,以下简称“动态库”)的导入库(Import Libary,以下简称“导入库”)。静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。动态库一般会有对应的导入库,方便程序静态载入动态链接库,否则你可能就需要自己LoadLibary调入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。导入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。


一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。在动态库的情况下,有两个文件,而一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。


————————————————————————————-



静态链接库(Lib)与动态链接库(DLL)的区别


静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。


静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了。但是若使用 DLL,该 DLL 不必被包含在最终 EXE 文件中,EXE 文件执行时可以“动态”地引用和卸载这个与 EXE 独立的 DLL 文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。




“每一个lib文件就是若干函数(假设只有函数)的定义”


lib库有两种,一种是包含了函数所在DLL文件和文件中函数位置的信息,称为导出库;一种是包含函数代码本身,一般现有的DLL,用的是前一种库;以前在DOS下的TC/BC等,是后一种库。包含函数原型声明的,是头文件(.h)。




“通过#include包含这些函数声明的头文件后,我们的应用程序就可以使用lib文件中的函数”


还要指定编译器链接相应的库文件。在IDE环境下,一般是一次指定所有用到的库文件,编译器自己寻找每个模块需要的库;在命令行编译环境下,需要指定每个模块调用的库。




“那他和直接给出那个函数定义的文件,比如.cpp文件,和头文件有什么区别,静态链接库有什么用”


cpp文件是源代码,库文件是编译后的二进制代码,比如你可以调用Windows的API,但是不能看到其源代码一样。




“还有不明白的是,静态链接库中的lib文件只要用到,则整个lib文件的内容都放进了exe文件中,那它是被编译进去还是链接的时候连接进去的呢?”


是在链接的时候将lib链接到目标代码中。


静态链接库(Lib)


在VC++6.0中new一个名称为libTest的static library工程,


并新建lib.h和lib.cpp两个文件,lib.h和lib.cpp的源代码如下:


//文件:lib.h


#ifndef LIB_H


#define LIB_H


extern “C” int add(int x,int y);   //声明为C编译、连接方式的外部函数


#endif




//文件:lib.cpp


#include “lib.h”


int add(int x,int y)


{



return x + y;


}




编译这个工程就得到了一个.lib文件,这个文件就是一个函数库,它提供了add的功能。将头文件和.lib文件提交给用户后,用户就可以直接使用其中的add函数了。




标准Turbo C2.0中的C库函数(我们用来的scanf、printf、memcpy、strcpy等)就来自这种静态库。




下面来看看怎么使用这个库,在libTest工程所在的工作区内new一个libCall工程。libCall工程仅包含一个main.cpp文件,它演示了静态链接库的调用方法,其源代码如下:


#include <stdio.h>


#include “..\lib.h”//不可丢失


#pragma comment( lib, “..\\debug\\libTest.lib” )  //指定与静态库一起连接


int main(int argc, char* argv[])


{



printf( “2 + 3 = %d”, add( 2, 3 ) );


}


静态链接库的调用就是这么简单,或许我们每天都在用,可是我们没有明白这个概念。代码中#pragma comment( lib , “..\\debug\\libTest.lib” )的意思是指本文件生成的.obj文件应与libTest.lib一起连接


——————————————————————————————-



用VC++生成静态库文件


今天闲着没事做,自己写了一点小笔记,不知道对于新手有没用,高手就不用看了,作为新手的我斗胆来发表一个笔记,就是静态库文件的封装过程,使用VC++6.0编写,下面是正文,也许我的用语并不专业



以前我们写C/C++源文件的时候,都是先将各个写好的源文件编译,编译生成的是目标文件机器码,即.obj文件.(目标文件的扩展名不一定是.obj文件).


我们调用的标准C/C++函数机器码实际被封装于标准C/C++静态库文件中的.即那些扩展名为.lib的文件中.


最后链接器将我们编译的各个目标文件里的机器码和静态库(标准C/C++库)中的函数机器码链接到一起形成一个扩展名为.exe的可执行文件模块.


在这里我们叙述将C/C++源文件编译链接成一个静态库文件,但它不是可执行模块,它体内含有可执行机器码


静态库文件就像一个仓库或者容器,里面封装了一些可执行机器码.这些机器码是我们用程序设计语言,比如C/C++源文件编译后生成的机器码.


一.下面将讨论将C/C++源文件编译并链接成一个静态库文件的过程,


在VC++6.0中选择File-New-Win32 Static Library,写好工程名创建好工作空间后再选择菜单中New-File来为工程添加C或者C++ 源文件.


假如我们为该工程添加了一个名为lib_c.c和一个名为lib_cpp.cpp的源文件


//lib_c.c中的内容


extern int Add(int x,int y) //该函数是一个外部函数,任何文件都可以访问它


{



return x+y;


}


extern int data_c


//这是一个外部全局变量,任何文件可以访问它


//lib_cpp.cpp中的内容


extern “C” int


reduce(int x,int y)//这里加了个”C”表示允许C源文件访问这个C++函数代码


{



return x-y;


}


extern “C” int data_cpp=2;


注意以下几点


(1)当“extern”关键字修饰在函数或全局变量的定义中时,表示该函数或全局变量任何文件可以访问,“extern”关键字可以省略不写,缺省下就是”extern”


当“extern”关键字修饰在函数声明或全局变量声明中时,表示限定当前文件只能引用用“extern”关键字修饰定义的函数或全局变量.


(2)当”static”关键字修饰在函数或全局变量的定义中时,表示该函数或全局变量只能由本文件中加了”static”关键字修饰的函数声明或全局变量声明来引用.


当”static”关键字修饰在函数声明或全局变量声明中时,表示限定当前文件只能引用用“static”关键字修饰定义的函数或全局变量.


(3)在CPP源文件的函数和全局变量定义中加了个”C”表示允许C源文件访问该函数和全局变量.如果是C++源文件访它们的话则可加可不加.注意这”C”要大写.


接下来就要将写好的C/C++源文件进行编译和链接,最后会生成一个扩展名为.lib的文件.该文件就是静态库文件了,该静态库文件是不能直接运行的,我们所编译的C/C++源文件的机器码就已经被封装进这个用VC++6.0创建的静态库文件里面去了.


二.如何将编写好的静态库文件像使用C/C++标准库那样使用,下面将继续讨论


1.用VC++6.0新建一个工程名为TEST,添加一个名为TEST.c的源文件到该工程,因为我们将测试一下,将我们编写的库文件里的函数或者全局变量的机器码链接到我们这个TEST.c源文件中去,假设我们生成的库文件名为TEST.lib,先拷贝如下范例代码到TEST.c中


//TEST.c


#include <stdio.h>


extern int


Add(int x,int y); //当前文件只能访问“extern”关键字修饰定义的Add函数


extern int


reduce(int x,int y);// //当前文件只能访问“extern”关键字修饰定义的reduce函数


#pragma comment(lib,”TEST.lib”) //指示链接器到字符串所表示的文件路径中去找库文件


int main()


{



printf(“%d\n”,Add(2,3));


printf(“%d\n”,reduce(3,2));


return 0;


}


这里我们要声明静态库中已知的函数或全局变量的声明


#pragma comment(lib,”TEST.lib”)这条指令告诉链接器到字符串所表示的路径下去找库文件,这里我将库文件放到了当前工程目录下.也可以不写这句.


还有一种方法,可以直接在VC++6.0中设置依次选择tools、options、directories、library files菜单或选项,填入库文件路径(只键入库文件所在目录路径而不能输入库文件名),这只是告诉链接器库文件所在目录的路径,还没告诉链接器库文件名,方法是VC++6.0中设置依次选择project-settings-link 在object/library modules: 这栏输入库文件名字然后就OK了


2.当用C++源文件的目标文件和库文件的代码链接时有一点小改变,这里就不浪费口舌了,假设我们新建了一个工程并添加了一个名为TEST.CPP的源文件,拷贝如下范例代码到TEST.CPP中


//TEST.cpp


#include <stdio.h>


extern “C” int


Add(int x,int y); //表示引用的是C函数代码


extern int


reduce(int x,int y);


#pragma comment(lib,”TEST.lib”)


int main()


{



printf(“%d\n”,Add(2,3));


printf(“%d\n”,reduce(3,2));


return 0;


}


在这个C++源文件里引用C函数代码同样要加个”C”,但是在C源文件引用C++函数代码不能加”C++”,编译会报错,只能在C++文件函数定义中加”C”.


只有C++才支持这种引用方式,也许因为只有C++兼容C而没有C兼容C++这一原则.

.h头文件是编译时必须的,lib是链接时需要的,dll是运行时需要的。

附加依赖项的是.lib不是.dll,若生成了DLL,则肯定也生成 LIB文件。如果要完成源代码的编译和链接,有头文件和lib就够了。如果也使动态连接的程序运行起来,有dll就够了。在开发和调试阶段,当然最好都有。

.h .lib .dll三者的关系是:

H文件作用是:声明函数接口

DLL文件作用是: 函数可执行代码

当我们在自己的程序中引用了一个H文件里的函数,编链器怎么知道该调用哪个DLL文件呢?这就是LIB文件的作用: 告诉链接器 调用的函数在哪个DLL中,函数执行代码在DLL中的什么位置,这也就是为什么需要附加依赖项 .LIB文件,它起到桥梁的作用。如果生成静态库文件,则没有DLL ,只有lib,这时函数可执行代码部分也在lib文件中

目前以lib后缀的库有两种,一种为静态链接库(Static Libary,以下简称“静态库”),另一种为动态连接库(DLL,以下简称“动态库”)的导入库(Import Libary,以下简称“导入库”)。静态库是一个或者多个obj文件的打包,所以有人干脆把从obj文件生成lib的过程称为Archive,即合并到一起。比如你链接一个静态库,如果其中有错,它会准确的找到是哪个obj有错,即静态lib只是壳子。动态库一般会有对应的导入库,方便程序静态载入动态链接库,否则你可能就需要自己LoadLibary调入DLL文件,然后再手工GetProcAddress获得对应函数了。有了导入库,你只需要链接导入库后按照头文件函数接口的声明调用函数就可以了。导入库和静态库的区别很大,他们实质是不一样的东西。静态库本身就包含了实际执行代码、符号表等等,而对于导入库而言,其实际的执行代码位于动态库中,导入库只包含了地址符号表等,确保程序找到对应函数的一些基本地址信息。

一般的动态库程序有lib文件和dll文件。lib文件是必须在编译期就连接到应用程序中的,而dll文件是运行期才会被调用的。如果有dll文件,那么对应的lib文件一般是一些索引信息,具体的实现在dll文件中。如果只有lib文件,那么这个lib文件是静态编译出来的,索引和实现都在其中。静态编译的lib文件有好处:给用户安装时就不需要再挂动态库了。但也有缺点,就是导致应用程序比较大,而且失去了动态库的灵活性,在版本升级时,同时要发布新的应用程序才行。在动态库的情况下,有两个文件,而一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。

————————————————————————————-



静态链接库(Lib)与动态链接库(DLL)的区别

静态连接库就是把(lib)文件中用到的函数代码直接链接进目标程序,程序运行的时候不再需要其它的库文件;动态链接就是把调用的函数所在文件模块(DLL)和调用函数在文件中的位置等信息链接进目标程序,程序运行的时候再从DLL中寻找相应函数代码,因此需要相应DLL文件的支持。

静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib 中的指令都全部被直接包含在最终生成的 EXE 文件中了。但是若使用 DLL,该 DLL 不必被包含在最终 EXE 文件中,EXE 文件执行时可以“动态”地引用和卸载这个与 EXE 独立的 DLL 文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。



“每一个lib文件就是若干函数(假设只有函数)的定义”


lib库有两种,一种是包含了函数所在DLL文件和文件中函数位置的信息,称为导出库;一种是包含函数代码本身,一般现有的DLL,用的是前一种库;以前在DOS下的TC/BC等,是后一种库。包含函数原型声明的,是头文件(.h)。




“通过#include包含这些函数声明的头文件后,我们的应用程序就可以使用lib文件中的函数”

还要指定编译器链接相应的库文件。在IDE环境下,一般是一次指定所有用到的库文件,编译器自己寻找每个模块需要的库;在命令行编译环境下,需要指定每个模块调用的库。




“那他和直接给出那个函数定义的文件,比如.cpp文件,和头文件有什么区别,静态链接库有什么用”


cpp文件是源代码,库文件是编译后的二进制代码,比如你可以调用Windows的API,但是不能看到其源代码一样。




“还有不明白的是,静态链接库中的lib文件只要用到,则整个lib文件的内容都放进了exe文件中,那它是被编译进去还是链接的时候连接进去的呢?”


是在链接的时候将lib链接到目标代码中。