跳转至

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)

7 gcc编译错误总结

7.1 错误:‘xxx’不是一个类型名

  • 分析:此类错误就是编译器并没有找到"xxx"类的定义
  • 原因:
  • 没有包含头文件
  • 多个文件中使用了相同的防止头文件重复包含的宏,导致某个头文件没有被包含进来

2 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 对两者都有效。

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