跳转至

gcc

GCC全称是GNU Compiler Collection,是GNU编译集。
CNU全称是GNU's Not Unix,GNU 是一个类 Unix 操作系统,包括Linux内核和其它GNU软件包和由第三方发布的自由软件。

1 gcc 介绍

GCC和gcc是不同的东西,gcc是指GNU c 编译器。g++是指c++编译器。
  • 功能:gcc 一般用来编译 c 文件,但也可以用来编译其它文件,默认以文件结尾来判断:
  • xxx.c:默认以编译 C 语言程序的方式编译此文件;
  • xxx.cpp:默认以编译 C++ 程序的方式编译此文件。
  • xxx.m:默认以编译 Objective-C 程序的方式编译此文件;
  • xxx.go:默认以编译 Go 语言程序的方式编译此文件;
  • gcc 也能通过 -x 指定编译语言,如 gcc -xc++ -lstdc++ 1.cpp

建议:虽然 gcc 可以编译其它语言,但还是以 gcc 编译 c 文件,以 g++ 编译 c++ 文件。因为 gcc 要编译 c++ 文件需要设置多个编译选项,这样不仅麻烦还可能出错,如下所示

//1.cpp
#include<iostream>
using namespace std;
int main(){
    cout<<"hello world!"<<endl;
}
  • 使用 gcc 编译:gcc -xc++ -lstdc++ 1.cpp
  • 使用 g++ 编译:g++ 1.cpp

2 源文件命令规范

  • c 源文件后缀为:.c
  • c 头文件后缀为:.h
  • c++ 源文件后缀包括: .cc .cp .cxx .cpp .CPP .c++ .C
  • c++ 头文件后缀包括:.h .hh .H .hp .hxx .hpp .HPP .h++ .tcc
  • c 预处理文件为:.i
  • c++ 预处理文件为:ii

3 gcc 常用指令

3.1 gcc 预处理、编译、汇编和链接 4 大步骤

  • 预处理 -E:g++ -E xxx.c,生成 xxx.i 预处理文件
  • 编译 -S:g++ -S xxx.i,生成 xxx.s 汇编文件
  • 汇编 -c:g++ -c xxx.s,生成 xxx.o 目标文件
  • 链接 g++ xxx.o -o xxx.exe,生成 xxx.exe 可执行文件(输入文件放在 g++ 之后,其它选项在输入文件之后,否则可能会包链接错误)
$ ls
1.cpp
$ cat 1.cpp 
#include<iostream>
using namespace std;
int main(){
        cout<<"hello world!"<<endl;
}
$ g++ -E 1.cpp
$ ls
1.cpp  1.i
$ g++ -S 1.i
$ ls
1.cpp  1.i  1.s
$ g++ -c 1.i
$ ls
1.cpp  1.i  1.o  1.s
$ g++ 1.o -o 1.exe
$ ls
1.cpp  1.exe  1.i  1.o  1.s
$ ./1.exe
hello world!

3.2 -c 选项

  • 功能:编译或汇编源文件,但不链接。链接阶段根本没有完成。最终输出是每个源文件的对象文件形式。输入的文件可以是 .c/cc.i.s ,输出则是 .o 文件。

3.3 -S 选项

  • 功能:编译但不汇编。最终输出是汇编文件形式。输入的文件可以是 .c/cc.i ,输出则是 .s 文件。

3.4 -E 选项

  • 功能:预处理阶段后停止;输出的是 .i 预处理文件。

3.5 -x 选项

  • 功能:显示指定输入文件的语言,如 -xc-xc++

3.6 选项 -o

  • 功能:指定生成文件名
gcc test.c -o test

3.7 选项 -s

  • 功能:删除所有符号和重定位信息删除所有符号和重定位信息。
gcc -s test.c -o test

3.8 选项 -L 指定静态库查找路径

g++ -L~/test libprint.a test.c 

3.9 选项 -l 添加链接库

# 优先链接动态库,找不到动态库再尝试链接静态库
g++ -L~/test -lprint test.c 

3.10 选项 -static 指定连接静态库而不是动态库

g++ -L~/test -static -lprint test.c 

3.11 选项 -I 指定头文件包含目录

g++ -I~/test/src print.c test.c

3.12 选项 -g 生成调试信息

g++ -g test.c 

3.13 编译多个文件

假设有 3 个文件:main.cpp print.cpp print.h - 方式一:直接生产可执行程序

$ ls
main.cpp  print.cpp  print.h
$ g++ main.cpp print.cpp -o main.exe
$ ls
main.cpp  main.exe  print.cpp  print.h
  • 方式二:生成目标文件,再链接
$ ls
main.cpp  print.cpp  print.h
$ g++ -c main.cpp
$ g++ -c print.cpp
$ ls
main.cpp  main.o  print.cpp  print.h  print.o
$ g++ main.o print.o -o main.exe
$ ls
main.cpp  main.exe  main.o  print.cpp  print.h  print.o

3.14 -std=支持标准范围

以下是 gcc10.0.2 支持的 c++ 标准 - -std=c++03 - -std=c++11 - -std=c++14 - -std=c++17 - -std=c++2a

3.15 选项 -save-temps 保存中间文间 (.i .s .o 文件)

$ ls
main.cpp  print.cpp  print.h

$ g++ main.cpp  print.cpp -save-temps
$ ls
a.out     main.ii  main.s     print.h   print.o  main.cpp  main.o   print.cpp  print.ii  print.s

4 编译选项

许多选项都有以“-f”或“-W”开头的长名称,例如,`-fmove-loop-invariants`,`-Wformat`等。其中大多数都有正和负形式;`-ffo`的否定形式是`-fno-foo`,`-Wformat`的否定形式是`-Wno-format`。

4.1 @file

  • 功能:从指定文件中读取命令行选项, 文件内多个选项用空格分隔;文件本身可能包含其他 @文件 选项;任何这样的选项都将被递归处理。

4.2 -W 编译警告

  • -Wall: 打开所有警告
  • -werror: 将警告转为错误(将触发编译报错,中断编译)
  • -w: 关闭所有警告(不建议使用)

4.3 忽略特定警告

开启警告通常是 -W + 警告名称,如 Wimplicit 是开启警告,而关闭警告通常是 -Wno- + 警告名称,如 Wno-implicit

4.4 -O 编译优化

编译器提供 4 个优化等级 -O0-O1-O2-O3,其中 -O0 表示没有优化,-O3 表示最高的优化等级

4.5 -D 声明宏、-U 取消宏

  • -Dmacro-D macro: 声明宏,作用同 #define macro
  • -Dmacro=defn-D macro=defn: 声明宏,作用同 #define macro=defn
  • -Umacro-U macro: 取消宏,作用同 #undef macro
  • -undef: 取消对任何非标准宏的定义

5 编译链接静态库动态库

Linux 上库命名格式,以 lib 起始,xxx 表示库名,.a 结尾是静态库,.so 结尾是动态库 - 静态库: libxxx.a - 动态库: libxxx.so

5.1 c++ 动态库静态库后缀

  • Linux
  • 静态库:xxx.a(.a 代表 archive)
  • 动态库:xxx.so(.so 代表 share object)
  • Windows
  • 静态库:xxx.lib
  • 动态库:xxx.dll

5.2 编译和链接静态库

  • -L 选项指定库路径,不指定默认系统/lib,/usr/lib 等路径

5.2.1 生成

  1. 将源文件生成目标文件
  2. 使用 ar 打包成静态库
$ g++ -c print.cpp
$ ls
main.cpp  print.cpp  print.h  print.o
$ ar rcs libprint.a print.o
$ ls
libprint.a  main.cpp  print.cpp  print.h  print.o

5.2.2 使用

  • -static 指定静态链接
  • -lxxx 指定静态库或 libxxx.a

注意:-o xxx.exe 应该放入最后,即 libxxx.a 不应该在 xxx.exe 后

$ g++ -static main.cpp libprint.a -o main.exe
$ ls
libprint.a  main.cpp  main.exe  print.cpp  print.h  print.o
shuhaiwen@shuhaiwen-PC:~/code/cpp$ ./main.exe
hello world!
$ g++ main.cpp -o main.exe -static -L /home/shuhaiwen/code/cpp/ -lprint
$ ls
libprint.a  main.cpp  main.exe  print.cpp  print.h  print.o
shuhaiwen@shuhaiwen-PC:~/code/cpp$ ./main.exe
hello world!

5.3 编译和链接动态库

5.3.1 生成

  • -fpic -shared
$ g++ -fpic -shared print.cpp -o libprint.so
$ ls
libprint.a  libprint.so  main.cpp  print.cpp  print.h

5.3.2 使用

  • 方式一:在代码内使用 dlfcn.h 提供的函数动态导入需要的函数
  • dlopen: 打开动态库
  • dlsym: 获取库函数
  • dlclose: 关闭动态库
  • 方式二:像静态库链接一样使用
  • 将动态库放入系统库目录下(例如 /usr/lib、/usr/lib64、/lib、/lib64)(不推荐)
  • .bash_profile 文件中导出 LD_LIBRARY_PATH,如 export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库路径
  • 加上选项 -Wl,-rpath=[.so路径],比如 g++ main.cpp -o main.exe -Wl,-rpath=/home/shuhaiwen/code/cpp/ -lprint

6 扩展

6.1 ldd 命令查看可执行程序依赖的共享库

$ ldd libprint.so
        linux-vdso.so.1 (0x00007ffd48119000)
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f94c2660000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f94c24dd000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f94c24c3000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f94c2302000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f94c2809000)

gcc 链接工具

1 内存和线程安全

这些是一些与内存和线程安全相关的运行时库,它们用于检测和防止 C 和 C++ 程序中的不同类型的错误。这些库是 GCC(GNU Compiler Collection)和 Clang(LLVM Compiler Infrastructure)等编译器工具链的一部分。以下是这些库的简要解释和区别:

  1. libasan(AddressSanitizer):用于检测内存错误,如缓冲区溢出、使用已释放的内存、内存泄漏等。libasan 通过在运行时检测内存访问,捕获并报告内存错误。它还提供了一些环境变量,允许你调整其行为和输出。

  2. libtsan(ThreadSanitizer):用于检测线程安全问题,如数据竞争和死锁。libtsan 通过跟踪程序中的线程操作,检测并报告线程之间的竞争条件。它可以帮助发现潜在的多线程问题,以及可能导致未定义行为的情况。

  3. liblsan(LeakSanitizer):用于检测内存泄漏问题。liblsan 会在程序退出时检查是否有未释放的内存块,并报告泄漏的内存。它可以帮助你发现程序中没有正确释放内存导致的泄漏问题。

  4. libubsan(UndefinedBehaviorSanitizer):用于检测未定义行为,如整数溢出、空指针解引用等。libubsan 可以帮助你发现代码中的一些编程错误,避免执行时的未定义行为。

这些工具和库在编译时和运行时帮助开发人员捕获和修复程序中的不同类型的错误。它们的主要目标是提高代码的质量、可靠性和安全性,以减少潜在的错误和漏洞。根据需要,你可以选择在编译时启用其中一个或多个库,并在开发和测试阶段使用它们来改进代码。

gcc 标准

1 c 标准历史版本

最初的 ANSI C 标准 (X3.159-1989) 于 1989 年获得批准并于 1990 年发布。该标准于 1990 年晚些时候被批准为 ISO 标准 (ISO/IEC 9899:1990)。
扩展的c标准,比如gnu90、gnu99、gnu11,这些标准时对同级别标准的扩展,比如gnu90就会支持部分c99、c11特性。gcc12.2默认就会开启-std=gnu17。
  • C89/C90:(原始版)原始的 ansi c 标准,gcc 选项使用 -ansi-std=c90 or -std=iso9899:1990
  • C94/C95:(修订版)主要修订 C89/C90 标准中的错误,gcc 选项使用 -std=iso9899:199409
  • C99:(新标准)gcc 选项使用 -std=c99or-std=iso9899:1999;1999 年作为 ISO/IEC 9899:1999 发布。(在开发过程中,这个标准版本的草稿被称为 C9X。)
  • C11:(新标准)gcc 选项使用 -std=c11or-std=iso9899:2011;2011 年作为 ISO/IEC 9899:2011 发布。(在开发过程中,这个标准版本的草案被称为 C1X。)
  • C17-std=c17or-std=iso9899:2017;2017 年编写了一个集成了更正的版本,并于 2018 年作为 ISO/IEC 9899:2018 发布;它被称为 C17,支持;
  • C2x:(新标准)gcc 选项使用 -std=c2x;C 标准的另一个版本,正在开发中。

2 c++ 标准历史版本

GCC 支持 1998 年发布的原始 ISO C++ 标准,以及 2011、2014、2017 年和大部分 2020 年的修订版。
您还可以使用 -std=gnu++98(对于带有 GNU 扩展的 C++98)或 -std=gnu++11(对于带有 GNU 扩展的 C++11)显式选择 C++ 语言的扩展版本,或 -std=gnu++14(对于带有 GNU 扩展的 C++14),或 -std=gnu++17(对于带有 GNU 扩展的 C++17),或 -std=gnu++20(对于 C+ +20 与 GNU 扩展)。gcc12.2默认开启-std=gnu++17。
  • C++98:(原始版)gcc 选项使用 -ansior-std=c++98;最初的 ISO C++ 标准作为 ISO 标准 (ISO/IEC 14882:1998) 发布。
  • C++03:(修订版)gcc 选项使用 -std=c++03;2003 年发布的技术勘误表 (ISO/IEC 14882:2003) 对 C++98 进行了修订。
  • C++11:(新标准)gcc 选项使用 -std=c++11;2011 年发布为 ISO/IEC 14882:2011 在发布之前,它通常被称为 C++0x
  • C++14:(新标准)gcc 选项使用 -std=c++14;2014 年发布为 ISO/IEC 14882:2014;在发布之前,它有时被称为 C++1y
  • C++17:(新标准)gcc 选项使用 -std=c++17;2017 年发布了 ISO/IEC 14882:2017。在发布之前通常被称为 C++1z
  • C++20:(新标准)gcc 选项使用 -std=c++20; 2020 年发布为 ISO/IEC 14882:2020;在发布之前,它有时被称为 C++2a。
  • C++23 :又被称为 C++2b。

3 警告选项

更多警告选项请参考官网https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Warning-Options.html#Warning-Options
  • -fsyntax-only:检查代码是否有语法错误,但除此之外不要做任何事情。
  • -fmax-errors=n:将错误消息的最大数量限制为 n,此时 GCC 会退出而不是尝试继续处理源代码。如果 n 为 0(默认值),则生成的错误消息的数量没有限制。如果还指定了 -Wfatal-errors,则 -Wfatal-errors 优先于该选项。
  • -w:禁止所有警告信息。
  • -Werror:将所有警告转换从错误。
  • -Werror=:将指定的警告转为错误。例如 -Werror=switch-Wswitch 控制的警告变成错误。
  • -Wno-error=:用于否定特定的警告。例如使用了 -Werror,再使用 -Wno-error=switch 使 -Wswitch 警告不是错误,即使使用了 -Werror
  • -Wfatal-errors:此选项导致编译器在发生第一个错误时中止编译,而不是尝试继续并打印更多错误消息。
  • -Wall:开启大部分警告,激活的警告信息如下。
-Waddress -Warray-bounds=1 (only with -O2) -Warray-compare -Warray-parameter=2 (C and Objective-C only) -Wbool-compare -Wbool-operation -Wc++11-compat -Wc++14-compat -Wcatch-value (C++ and Objective-C++ only) -Wchar-subscripts -Wcomment -Wdangling-pointer=2 -Wduplicate-decl-specifier (C and Objective-C only) -Wenum-compare (in C/ObjC; this is on by default in C++) -Wformat -Wformat-overflow -Wformat-truncation -Wint-in-bool-context -Wimplicit (C and Objective-C only) -Wimplicit-int (C and Objective-C only) -Wimplicit-function-declaration (C and Objective-C only) -Winit-self (only for C++) -Wlogical-not-parentheses -Wmain (only for C/ObjC and unless -ffreestanding) -Wmaybe-uninitialized -Wmemset-elt-size -Wmemset-transposed-args -Wmisleading-indentation (only for C/C++) -Wmismatched-dealloc -Wmismatched-new-delete (only for C/C++) -Wmissing-attributes -Wmissing-braces (only for C/ObjC) -Wmultistatement-macros -Wnarrowing (only for C++) -Wnonnull -Wnonnull-compare -Wopenmp-simd -Wparentheses -Wpessimizing-move (only for C++) -Wpointer-sign -Wrange-loop-construct (only for C++) -Wreorder -Wrestrict -Wreturn-type -Wsequence-point -Wsign-compare (only in C++) -Wsizeof-array-div -Wsizeof-pointer-div -Wsizeof-pointer-memaccess -Wstrict-aliasing -Wstrict-overflow=1 -Wswitch -Wtautological-compare -Wtrigraphs -Wuninitialized -Wunknown-pragmas -Wunused-function -Wunused-label -Wunused-value -Wunused-variable -Wuse-after-free=3 -Wvla-parameter (C and Objective-C only) -Wvolatile-register-var -Wzero-length-bounds
  • -Wextraor-W:开启额外的警告(这会启用一些 -Wall 未启用的额外警告标志。),激活的警告信息不仅包括下面部分,还包含以下几种
    • 使用 <、<=、> 或 >= 将指针与整数零进行比较。
    • (仅限 C++)枚举数和非枚举数都出现在条件表达式中。
    • (仅限 C++)模棱两可的虚基。
    • (仅限 C++)下标已声明为寄存器的数组。
    • (仅限 C++)获取已声明为寄存器的变量的地址。
    • (仅限 C++)基类未在派生类的复制构造函数中初始化。
-Wclobbered -Wcast-function-type -Wdeprecated-copy (C++ only) -Wempty-body -Wenum-conversion (C only) -Wignored-qualifiers -Wimplicit-fallthrough=3 -Wmissing-field-initializers -Wmissing-parameter-type (C only) -Wold-style-declaration (C only) -Woverride-init -Wsign-compare (C only) -Wstring-compare -Wredundant-move (only for C++) -Wtype-limits -Wuninitialized -Wshift-negative-value (in C++11 to C++17 and in C99 and newer) -Wunused-parameter (only with -Wunused or -Wall) -Wunused-but-set-parameter (only with -Wunused or -Wall)

4 调试选项

1. 更多调试选项参考官方文档https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Debugging-Options.html#Debugging-Options
2. 如果您使用多个 -g 选项,无论有无级别编号,只有最后一个选项是有效的。
title: -g和-ggdb区别?
- `-g`:该选项可以利用操作系统的“原生格式(native format)”生成调试信息。GDB 可以直接利用这个信息,其它调试器也可以使用这个调试信息
- `-ggdb`:能使GCC为GDB生成专用的更为丰富的调试信息,但是,此时就不能用其他的调试器来进行调试了 (如 ddx)
- `-g`生成的调试信息是通用的,大多数调试器都能使用,但`-gdb`只支持GDB调试器
  • -g[level]-ggdb[level]-gstabs[level]-gxcoff[level]-gvms[level]
    • level:可选 0、1、2、3 级别,如果不指定 level,默认时 2。如 -g 等价于 -g2
    • level 0 :不产生任何调试信息。-g0 否定 -g,也就相当于没有指定 -g 选项。(gdb 调试时提示 no debugging symbols found)
    • level 1 :产生的信息最少,不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储之用。回溯跟踪指的是监视程序在运行过程中的函数调用历史,堆栈转储则是一种以原始的十六进制格式保存程序执行环境的方法,两者都是经常用到的调试手段。
    • level 2 :允许使用只有 GDB 可以使用的额外调试信息,调试信息包括扩展的符号表、行号、局部 或外部变量信息。(默认级别)
    • level 3 :不仅包括 level 2 的所有内容,还支持源代码中定义的宏。
  • -gbtf:BTF 调试信息。 BTF 是 eBPF 目标的默认调试格式。在其他目标(如 x86)上,当通过各自的命令行选项显式启用两种调试格式时,可以生成 BTF 调试信息以及 DWARF 调试信息。

5 优化选项

更多优化选项参考官方文档https://gcc.gnu.org/onlinedocs/gcc-12.2.0/gcc/Optimize-Options.html#Optimize-Options
  • -O0:不进行优化(默认选项)
  • -O-O1:编译器会尝试减少代码大小和执行时间,但不执行任何需要大量编译时间的优化。
  • -O2:GCC 执行几乎所有支持的优化(不涉及空间速度)。
  • -O3:更近一步的优化
  • -Os:优化大小。 -Os 启用所有 -O2 优化,除了那些经常增加代码大小的优化:
  • -Og:优化调试体验。对于生成可调试代码来说,它是比 -O0 更好的选择,因为在 -O0 处禁用了一些收集调试信息的编译器通道。否则 -Og 启用所有 -O1 优化标志,除了那些可能干扰调试的标志。(调试建议选择)

6 环境变量

6.1 C_INCLUDE_PATH、CPLUS_INCLUDE_PATH、CPATH

  • C_INCLUDE_PATHCPLUS_INCLUDE_PATHCPATH 环境变量用于 C/C++ 搜索 include 目录
    • C_INCLUDE_PATH 只对预处理 C 语言有效,
    • CPLUS_INCLUDE_PATH 只对预处理 C++ 有效
    • CPATH 对两者都有效。

7 C++ 标准库的在 GCC 的支持