gcc的学习在C接触到linux以后就开始不断的学习,也知道了一些基本的用法,但是关于gcc的使用还是有很多值得我们加深的地方。gcc只是一个编译工具而已。也就相当于我们在windows环境下的visual c++等一样,区别是visual c++是基于IDE的,而gcc是这些IDE的基础。学习linux程序设计必然会学习gcc。
gcc实质是完成程序的编译和链接,程序的编译是指从一种文件类型转换到另一种文件类型的过程。一个C语言程序转换为可执行程序的基本步骤如下:
1、编写程序(vi,emacs等软件)
2、程序预编译(cpp)
3、编译成汇编程序(cc)
4、汇编程序(as)
5、链接程序(ld)
其中的这些过程都已经被gcc包含,我们在实际的编译过程中采用了gcc main.c -o main.exe即可实现一个程序的编译和链接。并不需要一步一步的实现,但是我们在分析的过程中又必须注意一个C语言文件的处理过程以及相应的处理程序。
关于gcc的基本含义用法就不再详细的说明了,我觉得最简单的使用方法是通过软件的help学习软件。
[gong@Gong-Computer test]$ gcc --help
Usage: gcc [options] file...
Options:
-pass-exit-codes Exit with highest error code from a phase
--help Display this information
--target-help Display target specific command line options
--help={target|optimizers|warnings|params|[^]{joined|separate|undocumented}}[,...]
Display specific types of command line options
(Use '-v --help' to display command line options of sub-processes)
--version Display compiler version information
-dumpspecs Display all of the built in spec strings
-dumpversion Display the version of the compiler
-dumpmachine Display the compiler's target processor
-print-search-dirs Display the directories in the compiler's search path
-print-libgcc-file-name Display the name of the compiler's companion library
-print-file-name=
Display the full path to library
-print-prog-name= Display the full path to compiler component
-print-multi-directory Display the root directory for versions of libgcc
-print-multi-lib Display the mapping between command line options and
multiple library search directories
-print-multi-os-directory Display the relative path to OS libraries
-print-sysroot Display the target libraries directory
-print-sysroot-headers-suffix Display the sysroot suffix used to find headers
-Wa, Pass comma-separated on to the assembler
-Wp, Pass comma-separated on to the preprocessor
-Wl, Pass comma-separated on to the linker
-Xassembler Pass on to the assembler
-Xpreprocessor Pass on to the preprocessor
-Xlinker Pass on to the linker
-combine Pass multiple source files to compiler at once
-save-temps Do not delete intermediate files
-save-temps= Do not delete intermediate files
-no-canonical-prefixes Do not canonicalize paths when building relative
prefixes to other gcc components
-pipe Use pipes rather than intermediate files
-time Time the execution of each subprocess
-specs= Override built-in specs with the contents of
-std= Assume that the input sources are for
--sysroot= Use as the root directory for headers
and libraries
-B Add to the compiler's search paths
-b Run gcc for target , if installed
-V Run gcc version number , if installed
-v Display the programs invoked by the compiler
-### Like -v but options quoted and commands not executed
-E Preprocess only; do not compile, assemble or link
-S Compile only; do not assemble or link
-c Compile and assemble, but do not link
-o Place the output into
-x Specify the language of the following input files
Permissible languages include: c c++ assembler none
'none' means revert to the default behavior of
guessing the language based on the file's extension
Options starting with -g, -f, -m, -O, -W, or --param are automatically
passed on to the various sub-processes invoked by gcc. In order to pass
other options on to these processes the -W options must be used.
For bug reporting instructions, please see:
<>.
从上面的结果可以知道基本的用法。
但是还是有几个需要注意的地方,这也是我们学习gcc时不经常使用,但又非常有用的几个用法。
1、采用gcc实现预编译,预编译可以实现代码的检查,特别是宏定义的检查,通过预编译检查实际的代码是否出错,这是非常有用的检查方式。
由于预编译以后宏定义被扩展了,这时对源码的分析就能找出代码宏定义等是否存在错误,特别时一些不容易发现的错误。
基本的实现形式为:gcc -E file.c > file.pre.c
[gong@Gong-Computer Example]$ vi main.c
采用重定向的方式改变输出流,便于检查错误所在。
[gong@Gong-Computer Example]$ gcc -E main.c > main.pre.c
[gong@Gong-Computer Example]$ vi main.pre.c
从上面的结果可以发现我们的宏已经实现了扩展。通过分析宏的扩展可以分析代码是否正确。
比如我将宏定义max(x,y)改写为max (x,y)就会出现下面的结果。如下图所示。
从856行的结果我们可以知道,上面的代码并不是我们需要的情况,这说明我们的代码存在问题,从而实现了宏定义的检测。这是非常有用的一种检测方式。
2、产生镜像文件
基本的实现方法是:注意Wl逗号后面跟着需要传递的参数,逗号后面不能存在空格,否则出现错误。
gcc -Wl,-Map=file.map file.c -o target
关于选项-Wl的使用可以参考help,这是我的一个截图
从上面说明可以知道-Wl用于传递参数给链接器。当然也有传递给汇编器和预编译的选项。
通过上面的选项可以得到一个镜像文件,通过打开镜像文件来程序的结构。
[gong@Gong-Computer Example]$ gcc -Wl,-Map=main.map main.c -o main.exe
[gong@Gong-Computer Example]$ vi main.map
上面只是其中的一部分,还有很多的内容。其中这些内容指出了程序的基本分布情况。
3、汇编程序
汇编语言是不可避免要学习的设计语言,但是很多时候并不需要完全手动的编写汇编语言,我们可以采用gcc实现一段程序的汇编形式,只需要选择正确的选项即可。
gcc -S file.c
实现如下:
[gong@Gong-Computer Example]$ gcc -S main.c
[gong@Gong-Computer Example]$ vi main.s
从上面的代码就知道了基本的汇编形式,当然也可以自己设计,但是该选项简化了汇编语言的设计。
4、在gcc中函数库,链接库的调用,这是比较难以掌握和容易出错的地方。
在静态编译的情况下:
gcc file.c -o file -Llibpath -llibname
gcc中-L主要是指明函数库的查找目录,-L后紧跟着目录而不是文件。-l后面紧跟着需要连接的库名,需要主要的是静态库通常是以libfile.a命名,这时-l后的库名只能是file,而不是libfile.a。这是需要注意的。一般情况下总是将-l放在最后。但是需要注意的是各个库之间的依赖关系。依赖关系没有搞清楚也会导致编译出现错误。
下面的代码如下:
- foo.c
- 1 #include<stdio.h>
- 2
- 3
- 4 extern void bar();
- 5
- 6 void foo()
- 7 {
- 8 printf("This is foo ().\n");
- 9
- 10 bar ();
- 11 }
bar.c
1 #include
2
3 void bar()
4 {
5 printf( " This is bar (). \n");
6 }
7
main.c
1 extern void foo();
2
3 int main()
4 {
5 foo();
6
7 return 0;
8 }
~
简要的介绍一些静态库的创建方式。
首先需要注意的时静态编译是指将一些库函数编译到程序中,这样会增加程序的大小。动态库则是在运行过程中添加到程序中,这样可以减小程序的大小。两种方式都有各自的优势。
静态库的创建:
gcc -c foo.c -o foo.o
gcc -c bar.c -o bar.o
创建的基本过程就是采用归档函数实现。
ar csr libfoo.a foo.o
ar csr libbar.a bar.o
从上面的程序我们可以知道foo程序依赖bar程序,而main程序则依赖foo程序,所以这样就形成了一定的关系,一般来说只有将依赖的库函数写在最右边才能保证其他的库函数依赖该库函数。
[gong@Gong-Computer test]$ gcc -o main main.c -L. -lbar -lfoo
./libfoo.a(foo.o): In function `foo':
foo.c:(.text+0x13): undefined reference to `bar'
collect2: ld returned 1 exit status
[gong@Gong-Computer test]$ gcc -o main main.c -L. -lfoo -lbar
以上的两个编译过程只是存在一个差异就是库的摆放顺序存在差别,第一种情况下由于foo依赖bar,而bar库不能被foo调用,因此出错。而第二种则满足foo依赖bar,main依赖foo的关系。其中的-L.表示库函数的搜索目录为当前目录。也可以换成其他的目录。
因此在gcc中添加库时,需要注意库名和库的顺序,最好采用一定的依赖关系图分析实现。具体的就要我们在设计程序时自己的考虑各个库函数之间的关系。
至于动态库的创建可以采用gcc实现。其中的-shared就是表明了该库是动态库,-fPCI是指支持PCI,file.o是指需要加载到库中的二进制文件。库名就是libname.so
gcc -shared -fPCI -o libname.so file.o
动态库的使用可以将创建好的动态库放在/usr/lib下,然后在函数中即可实现调用。
gcc的其他一些用法:
查找系统文件路径:gcc -v main.c
获得程序的依赖关系:gcc -M main.c ,其中包括了所有的依赖关系,在写makefile过程中写依赖关系通常不需要系统头文件,这时可以采用gcc -MM main.c去掉系统头文件的依赖关系。
- [gong@Gong-Computer test]$ gcc -M main.c
- main.o: main.c /usr/include/stdio.h /usr/include/features.h \
- /usr/include/sys/cdefs.h /usr/include/bits/wordsize.h \
- /usr/include/gnu/stubs.h /usr/include/gnu/stubs-32.h \
- /usr/lib/gcc/i686-redhat-linux/4.5.1/include/stddef.h \
- /usr/include/bits/types.h /usr/include/bits/typesizes.h \
- /usr/include/libio.h /usr/include/_G_config.h /usr/include/wchar.h \
- /usr/lib/gcc/i686-redhat-linux/4.5.1/include/stdarg.h \
- /usr/include/bits/stdio_lim.h /usr/include/bits/sys_errlist.h
- [gong@Gong-Computer test]$ gcc -MM main.c
- main.o: main.c
- [gong@Gong-Computer test]$
从上面的两个结果就可以知道两个选项的差别,这种差别在编写Makefile中的依赖关系时非常的有用。特别是第二种形式是比较重要的方式。
关于gcc的使用还是要多实践才有效果,才能准确的运用。