Linux编译器 —— gcc和g++

GCC和g++是GNUCompilerCollection的组成部分,gcc用于C编译,g++用于C++编译。两者本质上是驱动器,通过预处理器、编译器、汇编器和链接器完成编译工作。gcc和g++的区别在于处理.c和.cpp文件的方式及链接库的不同。编译选项如-E、-S、-c、-o、-v、-std、-w、-Wall、-Werror、-I、-L、-l、-g、-O等用于控制编译过程和优化等级。例如,-O选项可设置优化等级,影响代码性能和大小。
摘要由CSDN通过智能技术生成

目录

前言

1. gcc和g++的基本理解

2. gcc编译选项


前言

在了解编译器之前,首先需要了解有关程序编译的流程,详见

程序的编译过程(简述)_编译流程_七月不远.的博客-CSDN博客

1. gcc和g++的基本理解

首先要知道,GCC和gcc是两个不同的东西 

  • GCC:GNU Compiler Collection(GUN 编译器集合),最初是作为C语言的编译器(GNU C Compiler),现在可以编译C、C++、JAVA、Fortran、Pascal、Object-C、Ada等语言
  • gcc是GCC中的GUN C Compiler(C 编译器)
  • g++是GCC中的GUN C++ Compiler(C++编译器)

gcc和g++本质上并不是编译器,只是一个驱动器(driver),通过调用GCC编译器来完成对不同高级语言的编译工作,比如,用gcc编译一个.c文件的话,会有以下几个步骤:

  1. 调用预处理器 比如 cpp
  2. 调用真正的编译器 比如 cc or cc1
  3. 调用汇编器 比如 as
  4. 调用链接器 比如 ld

gcc 和 g++ 的区别无非就是调用的编译器不同, 并且传递给链接器的参数不同,更准确的说法是:gcc调用了C compiler,而g++调用了C++ compiler


g++:

  • g++ 会把 .c 文件当做是 C++ 语言 (在 .c 文件前后分别加上 -xc++ 和 -xnone, 强行变成 C++), 从而调用 cc1plus 进行编译
  • g++ 遇到 .cpp 文件也会当做是 C++, 调用 cc1plus 进行编译
  • g++ 还会默认告诉链接器, 让它链接上 C++ 标准库

gcc:

  • gcc 会把 .c 文件当做是 C 语言. 从而调用 cc1 进行编译
  • gcc 遇到 .cpp 文件, 会处理成 C++ 语言. 调用 cc1plus 进行编译
  • gcc 默认不会链接上 C++ 标准库

只要是 GCC 支持编译的程序代码,都可以使用 gcc 命令完成编译。可以这样理解,gcc 是 GCC 编译器的通用编译指令,因为根据程序文件的后缀名,gcc 指令可以自行判断出当前程序所用编程语言的类别 

但如果使用 g++ 指令,则无论目标文件的后缀名是什么,该指令都一律按照编译 C++ 代码的方式编译该文件。也就是说,对于 .c 文件来说,gcc 指令以 C 语言代码对待,而 g++ 指令会以 C++ 代码对待。但对于 .cpp 文件来说,gcc 和 g++ 都会以 C++ 代码的方式编译

因此并不能说gcc只能编译C代码,g++只能编译C++代码

不能用gcc编译C++程序的原因是:gcc在链接时不能自动和C++程序使用的库联接,因此只能使用g++来完成链接过程 

在Linux下,使用 gcc -v 命令可以查看gcc版本(g++类似),一般云服务器不经配置默认版本为4.8.5

使用 sudo yum install -y gcc-g++ 可以安装g++

2. gcc编译选项

假设我们有一个test.c源文件,使用gcc进行编译的最简单方式就是 gcc + .c文件名,默认生成一个a.out的可执行文件

gcc test.c

为了更好地控制编译过程,介绍如下选项

选项作用
-x指明输入文件的语言(否则gcc会根据文件后缀进行默认选择)
-E在预处理阶段后停止,不运行编译操作
-S在编译阶段后停止,不进行汇编操作
-c编译或者汇编源文件,不进行链接操作
-o后面接文件名,表示结果输出到哪个文件中
-v打印编译各阶段执行的命令,以及gcc的版本号
-ansi对.c文件,等价于-std=c90;对.cpp文件,等价于-std=c++98
-std=确定语言标准,只在编译C和C++文件时使用
-w不生成任何警告信息
-Wall生成所有警告信息
-Werror把警告信息作为错误处理
-I指定头文件的搜索路径
-L指定库文件的搜索路径
-l指定链接库文件名
-g生成调试信息
-O优化等级,可以取值为0、1、2、3
-D定义宏
-static此选项对生成的文件采用静态链接
-shared此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库

以下所有代码源文件均为test.c 

🔷 -x

#include <stdio.h>

int main()
{
    class A {};

    return 0;
}

对于这样一个C++文件,其后缀名却是.c,如果不在编译时添加指明语言的选项,使用如下编译命令会报错

[hqs@VM-8-2-centos 1]$ gcc test.c 
test.c: In function ‘main’:
test.c:5:5: error: unknown type name ‘class’
    5 |     class A {};
      |     ^~~~~
test.c:5:13: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘__attribute__’ before ‘{’ token
    5 |     class A {};

 使用 -x 选项能够成功编译

[hqs@VM-8-2-centos 1]$ gcc -x c++ test.c 
[hqs@VM-8-2-centos 1]$ ls
a.out  test.c

 🔷 -E -S -c -o

gcc -E test.c -o test.i    // 预编译完停下来,生成test.i文件
gcc -S test.i -o test.s    // 编译完停下来,生成test.s文件
gcc -c testis -o test.o    // 汇编完停下来,生成test.o文件
gcc test.o -o test.out     // 编译完成,生成test.out文件

🔷 -D

举个例子来介绍 -D 选项,对下面的test.c文件使用不同的命令进行编译

#include <stdio.h>

#ifdef MAX 
    int x = 1;
#endif

int main()
{
    printf("%d\n", x);                                                                                                                                               
    return 0;
}
gcc test.c -o test -D MAX  // 正确输出 1

gcc test.c -o test         // 报错

🔷 -O

-O选项可以对源文件进行优化,不同的数字代表不同的优化等级,数字越大,优化等级越高,在Linux中默认的优化等级是 -O0,就是说如果不带优化选项-O,编译码在编译时默认不进行优化

以下面test.c代码为例,通过查看生成的可执行文件的汇编代码,来观察不同优化等级下的表现

// 源代码
#include <stdio.h>

int main()
{
    int left = 10;
    int right = 20;
    int sum = left + right;
    printf("%d\n", sum);

    return 0;
}

-O0(默认选项)

0000000000401132 <main>:
  401132:	55                   	push   %rbp
  401133:	48 89 e5             	mov    %rsp,%rbp
  401136:	48 83 ec 10          	sub    $0x10,%rsp
  40113a:	c7 45 fc 0a 00 00 00 	movl   $0xa,-0x4(%rbp)
  401141:	c7 45 f8 14 00 00 00 	movl   $0x14,-0x8(%rbp)
  401148:	8b 55 fc             	mov    -0x4(%rbp),%edx
  40114b:	8b 45 f8             	mov    -0x8(%rbp),%eax
  40114e:	01 d0                	add    %edx,%eax
  401150:	89 45 f4             	mov    %eax,-0xc(%rbp)
  401153:	8b 45 f4             	mov    -0xc(%rbp),%eax
  401156:	89 c6                	mov    %eax,%esi
  401158:	bf 10 20 40 00       	mov    $0x402010,%edi
  40115d:	b8 00 00 00 00       	mov    $0x0,%eax
  401162:	e8 c9 fe ff ff       	callq  401030 <printf@plt>
  401167:	b8 00 00 00 00       	mov    $0x0,%eax
  40116c:	c9                   	leaveq 
  40116d:	c3                   	retq   
  40116e:	66 90                	xchg   %ax,%ax

在-O0级别的优化中,编译器十分“老实”,依次完成了如下操作:

  1. 先将10和20分别放进内存,再赋值给寄存器
  2. 在寄存器中完成加法,将结果写回内存
  3. 将结果从内存中取出,放到寄存器%eax中
  4. 再将%eax中的结果传给%esi作为printf函数的参数
  5. 调用printf函数输出

在这种情况下生成的代码与源代码几乎是一样的,只是在生成的代码中会添加一些调试信息,以便于调试程序,因此常用于开发和调试阶段,在生产环境中,通常会使用其他优化级别,以便生成更高效的代码,提高程序的性能

-O1和-O3

-O1选项表示编译器进行基本的优化,包括删除未使用的代码、复制传递参数、移动循环不变表达式等简单的优化。这种优化不会显著增加编译时间,但会略微提高代码的性能

0000000000401132 <main>:
  401132:	48 83 ec 08          	sub    $0x8,%rsp
  401136:	be 1e 00 00 00       	mov    $0x1e,%esi
  40113b:	bf 10 20 40 00       	mov    $0x402010,%edi
  401140:	b8 00 00 00 00       	mov    $0x0,%eax
  401145:	e8 e6 fe ff ff       	callq  401030 <printf@plt>
  40114a:	b8 00 00 00 00       	mov    $0x0,%eax
  40114f:	48 83 c4 08          	add    $0x8,%rsp
  401153:	c3                   	retq   
  401154:	66 2e 0f 1f 84 00 00 	nopw   %cs:0x0(%rax,%rax,1)
  40115b:	00 00 00 
  40115e:	66 90                	xchg   %ax,%ax

可以看到生成的可执行文件汇编代码量明显减少,编译器的行为是:

  1. 直接计算出了10+20的结果,并把这个结果30放到寄存器%esi中作为参数(-O0中第4步)
  2. 调用printf输出

对于-O3选项,编译器进行更高级别的优化,包括函数内联、循环展开、算法改进等复杂的优化。这种优化可以显著提高代码的性能,但会导致编译时间显著增加

-O3选项下的汇编代码如下,可以看到会比-O1选项的代码更短

0000000000401060 <main>:
  401060:	48 83 ec 08          	sub    $0x8,%rsp
  401064:	be 1e 00 00 00       	mov    $0x1e,%esi
  401069:	bf 10 20 40 00       	mov    $0x402010,%edi
  40106e:	31 c0                	xor    %eax,%eax
  401070:	e8 bb ff ff ff       	callq  401030 <printf@plt>
  401075:	31 c0                	xor    %eax,%eax
  401077:	48 83 c4 08          	add    $0x8,%rsp
  40107b:	c3                   	retq   

因此,一般来说,如果开发者注重编译时间,则可以选择-O1选项,如果注重程序的性能,则可以选择-O3选项。当然,在具体的应用场景中,不同的优化级别可能会产生不同的效果,需要根据实际情况进行选择

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值