04 ----- gcc相关介绍
GCC学习材料
GCC使用很少,但是这玩意又经常使用,那么记录它就非常关键了。
一、GCC简介
GCC是一个免费的C编译器,可以编译C、C++、Go,它是Linux等著名软件的基础,你查看你的Ubuntu系统版本,你会发现前面是GNU,如 :
Linux dell-PowerEdge-T640 5.4.0-90-generic #101~18.04.1-Ubuntu SMP Fri Oct 22 09:25:04 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
GNU是免费软件的发起项目,而GCC就是这个项目的C编译器!
Richard Stallman这个人是free运动发起者,也直接导致了后面的开源软件运动。
GCC实现编译后跨平台,这对于开发者非常重要!
二、编写编译C、C++程序
书写一个C程序
# include <stdio.h>
// 文件名: test.c
int main(void)
{
printf("Hello, world!\n");
return 0;
}
编译此文件
$ sudo gcc -Wall test.c -o hello
这里是使用gcc
工具进行编译,参数Wall
是在assembler
、linker
等阶段产生尽可能详细的警告信息;o
是指定输出的编译对象,这里传参hello
,它最终是一个可执行文件。
-W
是警告,all
是全部
执行hello
$ ./hello
Hello, world!
2.1 编译多个文件
$ sudo cp test.c main.c
$ sudo vim main.c
# include <stdio.h>
# include "hello.h"
// 程序名: main.c
int main(void)
{
hello("Hello, world!\n");
return 0;
}
$ sudo vim hello.h
void hello(const char* string);
$ sudo vim test.h
# include <stdio.h>
// 程序名: test.c
void hello(const char* string)
{
printf("%s", string);
}
$ sudo gcc -Wall main.c test.c -o newhello
$ ./newhello
Hello, world!
如上所示,我们可以直接罗列多个c文件,先后顺序是可以调换的!
注意: 导入自己写的头文件,需要使用引号,系统的最好使用尖括号<>
2.2 文件独立编译
2.2.1 编译为目标文件再编译为可执行文件
我们先将独立文件编译为目标文件,最好再将目标文件编译为可执行文件。
$ sudo gcc -Wall -c main.c
$ sudo gcc -Wall -c test.c
$ ll
drwxr-xr-x 2 root root 4096 Dec 10 14:28 ./
drwxr-xr-x 38 1002 1002 4096 Dec 10 10:58 ../
-rwxr-xr-x 1 root root 8296 Dec 10 11:06 hello*
-rw-r--r-- 1 root root 32 Dec 10 13:45 hello.h
-rw-r--r-- 1 root root 125 Dec 10 13:54 main.c
-rw-r--r-- 1 root root 1544 Dec 10 14:27 main.o
-rwxr-xr-x 1 root root 8360 Dec 10 14:12 newhello*
-rw-r--r-- 1 root root 103 Dec 10 14:03 test.c
-rw-r--r-- 1 root root 1552 Dec 10 14:28 test.o
根据目标文件生成可执行文件
$ sudo gcc main.o test.o -o hello1
$ ./hello1
Hello, world!
注:gcc对目标文件顺序没有要求!
手动生成每个目标文件明显是非常麻烦的,这不符合我们编程人员的操作习惯,而且后期再编译时我们也很难记住需要编译哪些目标对象。
2.2.2 使用make进行编译
三、gcc参数说明
安装好gcc之后,你开源使用gcc --help
查看帮助文档!
$ gcc --help
Usage: gcc [options] file...
Options:
……
--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=<lib> Display the full path to library <lib>.
-print-prog-name=<prog> Display the full path to compiler component <prog>.
-print-multiarch Display the target's normalized GNU triplet, used as
a component in the library path.
-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,<options> Pass comma-separated <options> on to the assembler.
-Wp,<options> Pass comma-separated <options> on to the preprocessor.
-Wl,<options> Pass comma-separated <options> on to the linker.
-Xassembler <arg> Pass <arg> on to the assembler.
-Xpreprocessor <arg> Pass <arg> on to the preprocessor.
-Xlinker <arg> Pass <arg> on to the linker.
-save-temps Do not delete intermediate files.
-save-temps=<arg> 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=<file> Override built-in specs with the contents of <file>.
-std=<standard> Assume that the input sources are for <standard>.
--sysroot=<directory> Use <directory> as the root directory for headers
and libraries.
-B <directory> Add <directory> to the compiler's search paths.
-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 <file> Place the output into <file>.
-pie Create a position independent executable.
-shared Create a shared library.
-x <language> 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.
上面是部分帮助文档。
参数说明
-
-v
or--version
展示编译器的版本信息;也可以在编译过程中显示详细信息。如
sudo gcc -v -Wall test.c main.c -o newhello
,这样会显示非常详细的编译过程,日志也会非常长
四、链接外部库
4.1 外部静态库
生成外部库
$ sudo ar cr libm.a main.o test.o
则我们生成了libm.a
库文件
查看库文件
$ sudo ar t libm.a
main.o
test.o
从库生成可执行文件
$ sudo gcc -Wall libm.a -o hello2
$ ./hello2
Hello, world!
当然你可以基于库做一些修改,这才是注意的使用方式,即在项目中使用库。
如我们需要使用公司提供的第三方库,则可以使用如下命令:
$ gcc -Wall main.c /usr/lib/libm.a -o calc
这里的/usr/lib/libm.a是一个库,和win下面的lib库是一样的(dll是动态链接库)。
上面的命令也等价于
$ gcc -Wall main.c -lm -o calc
也就是说-lm
等价于/usr/lib/libm.a
,为什么呢?首先,-l
是指定到默认路径下进行查找,m
是库的名字,因为我们定义库的时候命名一般 采用lib + name
的形式。
默认查找路径即我们使用# include <stdio.h>
时要搜索的路径!
常见错误
file.h: No such file or directory
就是在默认查找路径下找不到这个文件;/usr/bin/ld: cannot find library
就是在默认查找路径下找不到这个库;
如果库不在默认路径下,我们需要添加路径扩大搜索范围
-
使用
-I
指定头文件搜索路径; -
使用
-L
指定库文件搜索路径; -
你也可以在
.bash_profile
中设置环境变量:C_INCLUDE_PATH
指定C头文件位置;CPLUS_INCLUDE_PATH
指定 C++头文件位置; -
你也可以在
.bash_profile
中设置环境变量:LIBRARY_PATH
指定库文件路径 -
如果两种方式都指定了,优先搜索顺序是:
命令行指定 > 环境变量 > 默认路径 推荐使用第一种
4.2 外部动态库
静态库在被调用时,每次被调用就会copy一份到项目,如果有两个项目调用,则会有两份在内存中,这明显是一种浪费,动态库就是弥补了这种不足。动态库是给每个项目一份指针,供其使用。使用动态库生成的可执行文件执行时必须要能够找到动态库,因为编译时,没有打包动态库。如果你没有将其放置在默认查找路径下,那么就需要设置查找路径的环境变量LD_LIBRARY_PATH
.
4.2.1 创建动态链接库
首先查看帮助文档
$ gcc --help
……
-shared Create a shared library.
……
$ gcc -shared --help
# shared参数没有帮助文档
生成动态链接库
$ sudo gcc -Wall -shared -fPIC main.o test.o -o libhello.so
打包动态库正常执行!这里只有fPIC
参数比较陌生,下面是《关于-fPIC, -fpic, -fpie, -fPIE的一点理解》关于它的介绍:
- -fPIC与-fpic都是在编译时加入的选项,用于生成位置无关的代码(Position-Independent-Code)。这两个选项都是可以使代码在加载到内存时使用相对地址,所有对固定地址的访问都通过全局偏移表(GOT)来实现。-fPIC和-fpic最大的区别在于是否对GOT的大小有限制。-fPIC对GOT表大小无限制,所以如果在不确定的情况下,使用-fPIC是更好的选择。
- -fPIE与-fpie是等价的。这个选项与-fPIC/-fpic大致相同,不同点在于:-fPIC用于生成动态库,-fPIE用与生成可执行文件。再说得直白一点:-fPIE用来生成位置无关的可执行代码。
4.2.2 使用动态链接库
生成可执行文件
$ sudo gcc -Wall main.c -L. -lhello -o hello3
$ ./hello3
Hello, world!
当然这里你也可以不指定需要编译的文件main.c
因为我们动态库本来就打包了它:
$ sudo gcc -Wall -L. -lhello -o hello4
$ ./hello4
Hello, world!
完!