04 ----- gcc相关介绍

GCC学习材料


作者:elfin   参考资料:播布客


GCC使用很少,但是这玩意又经常使用,那么记录它就非常关键了。


Top --- Bottom

一、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实现编译后跨平台,这对于开发者非常重要!


Top --- Bottom

二、编写编译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是在assemblerlinker等阶段产生尽可能详细的警告信息;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进行编译


Top --- Bottom

三、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,这样会显示非常详细的编译过程,日志也会非常长


Top --- Bottom

四、链接外部库

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!

Top --- Bottom

完!

posted @ 2021-12-11 11:13  巴蜀秀才  阅读(469)  评论(0编辑  收藏  举报