1.GCC简介
文件扩展名:
*.c:该类文件为C语言的源文件
*.h:该类文件为C语言的头文件
*.i:该类文件为预处理后的C文件
*.cc:该类文件为C++语言的源文件
*.cpp:该类文件为C++语言的源文件
*.s:该类文件为汇编语言的源文件
*.o:该类文件为汇编后的目标文件
*.a:该类文件为静态库
*.so:该类文件为共享库
a.out:该类文件为链接后的输出文件
进行程序编译的时候,头文件路径和库文件路径是编译器默认查找的地方
头文件:/usr/local/include,/usr/lib/gcc/i486-linux-gnu/4.1.2/include,/usr/include
库文件:/usr/lib/gcc/i486-linux-gnu/4.1.2/ ,......,/lib,/usr/lib
2.编译程序的基本知识
GCC编译器将预编译、编译和优化、汇编、链接这4个步骤合并为1个。
目标文件是指经过编译器的编译生成的CPU可识别的二进制代码,但是目标文件一般不能执行,因为其中的一些函数过程没有相关的指示和说明。可执行文件就是目标文件与相关的库链接后的文件,它是可以执行的。
所有的目标文件必须用某种方式组合起来才能运行,这就是链接的作用。目标文件中通常仅解析了文件内部的变量和函数,对于引用的函数和变量还没有解析,这需要将其他已经编写好的目标文件引用进来将没有解析的变量和函数进行解析,通常引用的目标就是库。链接完成后会生成可执行文件。
3.单个文件编程成可执行文件
gcc -o hello hello.c
GCC先将C文件编译成目标文件,然后将目标文件链接成可执行文件,最后删除目标文件。-o选项表示可以使编译程序生成指定的可执行文件名,如果没有-o选项,则默认生成a.out。
4.编译生成目标文件
gcc -c hello.c
会生成一个hello.o的目标文件
gcc -c -o test.o hello.c
生成一个test.o的目标文件
gcc -c file1.c file2.c file3.c
生成3个file1.o file2.o和file3.o
5.多文件编译
GCC可以自动编译多个文件,不管是目标文件还是源文件,都可以使用同一个命令编译到可执行文件中。
源文件string.c,包含了用于计算的StrLen()函数。
#define ENDSTRING '\0'
int StrLen(char *string)
{
int len = 0;
while(*string ++ != ENDSTRING)
len++;
return len;
}
源文件main.c,调用string.c中的StrLen()函数
#include
extern int StrLen(char* str);
int main(int argc, char const *argv[])
{
char src[] = "hello dymatic";
printf("string length is:%d\n", StrLen(src));
return 0;
}
则使用gcc -o test string.c main.c
生成可执行文件test
也可以先将源文件编程目标文件,然后进行链接。
gcc -c string.c main.c
gcc -o test string.o main.o
6.预处理
预处理过程将源文件中的头文件包含进源文件中,并且将文件中定义的宏进行扩展。
gcc -o string.i -E string.c
将源文件预编译后生成的中间结果文件称为string.i。
string.i的内容如下:
# 1 "string.c"
# 1 ""
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 30 "/usr/include/stdc-predef.h" 3 4
# 1 "/usr/include/i386-linux-gnu/bits/predefs.h" 1 3 4
# 31 "/usr/include/stdc-predef.h" 2 3 4
# 1 "" 2
# 1 "string.c"
int StrLen(char *string)
{
int len = 0;
while(*string ++ != '\0')
len++;
return len;
}
7.编译成汇编语言
编译过程将用户可识别的语言翻译成一组处理器可识别的操作码,通常翻译成汇编语言。汇编语言通常和机器操作码之间是一种一对一的关系。
gcc -S string.c
生成汇编语言文件string.s。第一行内容是C语言的文件名,第三行和第四行是文件中的函数描述。
.file "string.c"
.text
.globl StrLen
.type StrLen, @function
StrLen:
.LFB0:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $16, %esp
movl $0, -4(%ebp)
jmp .L2
.L3:
addl $1, -4(%ebp)
.L2:
movl 8(%ebp), %eax
leal 1(%eax), %edx
movl %edx, 8(%ebp)
movzbl (%eax), %eax
testb %al, %al
jne .L3
movl -4(%ebp), %eax
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
.LFE0:
.size StrLen, .-StrLen
.ident "GCC: (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1"
.section .note.GNU-stack,"",@progbits
8.生成和使用静态链接库
静态库是obj文件的一个集合,通常静态库以".a"为后缀。静态库有程序ar生成,现在大部分程序都在使用动态库。
静态库的优点可以在不用重新编译程序库代码的情况下,进行程序的重新链接,这节约了编译过程的时间。但是由于现在系统的强大,编译的时间已经不是问题。静态库的另一个优势是开发者可以提供库文件给使用的人员,不用开放源代码,这是库函数提供者经常采用的手段。理论上静态库的执行速度要比共享库和动态库要快(1%~5%)。
ar -rcs libstr.a string.o
使用ar -r选项,可以创建库,并把目标文件插入到指定库中。上面将string.o打包为库文件libstr.a。
gcc -o test main.c libstr.a
对main.c进行链接的时候,需要使用之前已经编译好的静态链接库libstr.a。
或者
gcc -o test main.c -lstr
-l库名,库名是不包含函数库和扩展名的字符串。
上面的命令系统会在系统默认的路径下查找str函数库,并把它链接到要生成的目标程序上。系统会提示无法找到库函数的路径。例如库文件和当前编译文件在同一目录下:
gcc -o test main.c -L./ -lstr
这样就可以找到库函数。
9.生成动态链接库
动态链接库是程序运行时加载的库,当动态链接库正确安装后,所有的程序都可以使用动态库来运行程序。动态链接库中是目标文件的集合,目标文件在动态链接库中的组织方式是按照特殊方式形成的。动态链接库的名称有别名(由一个前缀lib,然后是库的名字,在加上一个后缀“.so”构成。真名(动态链接库的真是名字,一般总是在别名的基础上加上一个小版本号、发布版本等构成。除此之外,还有一个链接名,即程序链接时使用的库的名字。
gcc -shared -fPIC -Wl,-soname,libstr.so -o libstr.so.1 string.c
-soname,libstr.so表示生成动态库时的别名是libstr.so
-o libstr.so.1选项则是生成名字为libstr.so.1的实际动态链接库文件
-shared告诉编译器生成一个动态链接库
-fPIC:使得gcc生成的代码是位置无关的
生成动态链接库后很重要的就是要安装,将生成的动态链接库复制到系统默认的动态链接库的搜索路径下,通常有/lib,/usr/lib,/usr/local/lib,放到之上任何一个目录下都可以。
ldconfig命令的作用是在系统的默认搜索路径,和动态链接库配置文件中列出的目录里搜索动态链接库,创建动态链接库转入程序需要的链接和缓存文件,搜索完毕后,将结果写入缓存文件/etc/ld.so/cache中,文件中保存的是已经排好序的动态链接库名字列表。
当用户的目录并不在系统动态链接库配置文件/etc/ld.so.conf中指定的时候,可以使用ldconfig命令显示指定要扫描的目录,将用户指定目录中的动态链接库放入系统中进行共享。
ldconfig 目录名。
gcc -o test main.c -L./ -lstr
使用动态链接库,-L指定动态链接库的路径,-lstr链接库函数str。
但是运行test一般会出现不能加载动态链接库,
解决方法:
(1).将动态链接库的目录放到程序搜索路径中,将库的路径加到环境变量LD_LIBRARY_PATH中实现。
export LD_LIBRARY_PATH=/home/xieweichong/usaco: $LD_LIBRARY_PATH
将存放库文件libstr.so的路径/home/xieweichong/usaco加入到搜索路径中。
(2).使用ld-Linux.so.2来加载程序。
/lib/ld-linux.so.2 --library-path 路径 程序名
如果系统的搜索路径下同时存在静态链接库和动态链接库,默认会链接动态链接库,如果需要强制链接静态链接库,需要加上“-static”
gcc -o testdl main.c -static -ldl
10.动态加载库
动态加载库和一般的动态链接库所不同的是,一般动态链接库在程序启动的时候就要寻找动态库,找到库函数;而动态加载库可以用程序的方法来控制什么时候加载。动态加载库主要有函数dlopen()、dlerror()、dlsym()和dlclose()来控制动态库的使用。
void* dlopen(const char *filename, int flag);
按照用户指定的方式来打开动态链接库,函数的返回值为库的指针。
使用动态链接库的目的是调用其中的函数,完成特定的功能。函数dlsym()可以获得动态链接库中指定函数的指针,然后可以使用这个函数指针进行操作。函数dlsym()的原型如下,其中参数handle为dlopen()打开动态链接库后返回的句柄,参数symbol为函数的名称,返回值为函数指针。
void* dlsym(void *handle,char *symbol);
dlopen()来打开动态链接库,使用dlerror()来判断打开是否错误。使用函数dlsym()来获得动态链接库中的某个函数。
#include
#include
int main(int argc, char const *argv[])
{
char src[] = "hello dymatic";
int (*pStrLenFun)(char *str);
void *phandle = NULL;
char *perr = NULL;
phandle = dlopen("./libstr.so",RTLD_LAZY);
if(!phandle)
printf("Failed Load library!\n");
perr = dlerror();
if(perr != NULL)
{
printf("%s\n", perr);
return 0;
}
pStrLenFun = dlsym(phandle,"StrLen");
perr = dlerror();
if(perr != NULL)
{
printf("%s\n", perr);
return 0;
}
printf("the string length is: %d\n", pStrLenFun(src));
dlclose(phandle);
return 0;
}
使用gcc -o testdl main.c -ldl
编译上述文件的时候要需要动态链接库libdl.so。
使用动态加载库和动态链接库的结果是一致的。