gcc编译器

本文深入探讨GCC编译器的功能和使用方法,包括编译选项、调试技巧、优化策略及多文件编译流程。介绍了gcc、g++、gprof等工具的高级应用,适合初学者和有经验的开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

gcc编译器

gcc作为linux平台下的标准C编译器,功能强大;

gcc test1.c

注:旧版本gcc编译器编译时会出现警告信息,最新版本gcc4.4默认不会提示,加-Wall参数后会提示警告信息,gcc仍能完成编译程序;编译完成之后gcc会创建一个可执行文件 a.out,并执行;

./a.out

使用选项-o来改变编译后的文件名,如

gcc -o test1 test1.c 
./test1

gcc选项概述

gcc命令完整格式:

gcc [options] [filename]

命令行按编译选项(参数)指定的操作对给定的文件(filename)进行编译处理。在 gcc后面可有多个编译选项,同时进行多个编译操作

选项描述
-x languageC 、C++、汇编
-c只编译和汇编,不连接
-S编译,不汇编和连接
-E预处理,不编译、汇编、连接
-o file1 file2将file2编译成可执行文件file1
-L library指定所使用的库文件
-I directoryinclude文件搜索指定目录
-w禁止警告信息
-Wall显示附加警告信息
-g显示排错信息以便于调试
-pg产生gprof所需信息
-p产生prof所需信息
-O优化编译出的代码
-O2进行比-O高级一级的优化
-O3产生更高级的优化
-v显示gcc版本
-m***优化不同的微处理器
-pedantic严格要求符合ANSI标准

选项使用介绍

-c

gcc只把源码(.c)编译成目标代码(.o),跳过连接一步;能使编译多个C程序时的速度更快且更加容易管理,此选项默认时,gcc建立目标代码文件只有一个.o的扩展名

gcc -c test.c

-S

gcc在C程序文件产生了汇编语言文件后停止编译,汇编语言文件的默认扩展名为.s

gcc -S test.c

-E

指示编译器只对输入的文件进行预处理,且预处理的输出将被送到标准输出而不是存储在文件里。

-v

得到gcc版本相关信息,确定所用的是ELF还是a.out

两者都是二进制格式,ELF格式增长的灵活性超过了a.out,而且ELF格式设置共享程序库上拥有更大的便利性。

-m***
它是一类优化编译的选项,其中***代表的是用户所用的中央处理器的型号,如-m486告诉gcc把正在编译的程序视作转为486微处理器所编写的,当它和用户机器的中央处理器型号正好匹配时,将可取得最佳的优化效果。

调试标记

使用调试符号来编译程序,gcc在目标文件(.o)和创建可执行文件中插入额外信息,使得gdb能够判断编译过的代码和程序源码之间的关系,没有该信息gdb将无法判断源程序中的哪行代码在被执行。在默认情况下,调试符号不会编译到程序中,这会增大可执行文件的体积,调试之后,不用重新编译程序。

调试符号的使用可能与优化不兼容,两者混用,可能导致调试的混乱和失败。在编译一段写好的代码时,尽量避免使用-o选项和优化开关-f选项。可使用gcc的-g选项来产生调试符号,是一个默认的选项:

gcc -g -Wall -o test test.c

使用gdb调试器,ggdb3告诉gcc,使用gdb的扩展产生调试符号。其中“3”表示使用三级(最高级)调试信息,命令

gcc -ggdb3 -Wall -o test test.c

调试符号示例

#include <stdio.h>
int main(void){
  int input = 0;
  printf("Enter an integer: ");
  scanf("%d",&input);
  printf("Twice the number you supplied is %d\n", 2*input);
  return 0;
}

编译程序

gcc -Wall -o test2 test2.c
./test2

得到如下输出:

Enter an integer:5
Segmentation fault

使用调试符号来编译他

gcc -ggdb3 -Wall -o test2 test2.c

编译允许使用储存信息,可在bash中使用如下命令来指定core文件大小的最大值,该命令表示无大小限制。

ulimit -c unlinmited

得到输出

Enter an integer:5
Segmentation fault(core dumped)

输出错误相同,多了一个core.***文件,****为进程编号(编译后的程序都是被装载到进程中执行的),该文件告诉你到底错在哪。

将加载程序和core文件到gdb分析

gdb test2 core.***
program teminated with signal 11,Segmentation fault.
[New process 9868]
#0 0x004cb1c0 in_IO_vfscanf_internal () form /lib/libc.so.6
(gdb)

进入gdb调试环境,输入run或者r命令,回车,程序开始在调试状态下运行:

(gdb) r
Starting program: /../../test2
Enter an integer:5

分析会得到一段输出:

program teminated with signal 11,Segmentation fault.
[New process 9868]
#0 0x004cb1c0 in_IO_vfscanf_internal () form /lib/libc.so.6
(gdb)

从上面可知,错误发生在调用scanf(IO_vfscanf_internal())时,程序是因为段错误而导致崩溃,说明内存操作出了问题。

更加详尽的信息,使用以下语句:

(gdb) bt

得到如下输出:

#0 0x004cb1c0 in_IO_vfscanf_internal () form /lib/libc.so.6
#1
#2 0x0804842b in main() at test2.c:7
(gdb)

前两句是由C函数库来完成, 跳过这两句。由第三局知道程序test2.c的第6行出现错误。找到出错原因,改正,重新编译。

gcc优化代码

优化是现代C编译器的一个最令人心动的特性。优化时编译器的一部分,它可以检查和组合编译器生成的代码,指出未达到最优的部分,重新生成它们,从而使用户编写的程序更加完美且节省空间。gcc拥有强大且是可配置的优化器,从而能够对程序进行优化处理。

使用高级gcc选项

管理大型项目

C为程序员提供一个一种把文件分开的方法,通过使用分离的C模块、函数或被包含在不同的被编译过的.c文件里的数据,把工程分成符合逻辑的不同部分

假定程序3个模块,分为test2_1.c,test2_2.c,test2_3.c,可使用以下方法 编译整个程序:

gcc -Wall -o program test2_1.c test2_2.c test2_3.c

gcc将编译每个.c文件,并把它们连接起来称为一个可执行文件,若内容稍有改动,需要重新编译全部程序;

将编译分成独立的步骤,先编译每一程序,使用gcc的-c选项,程序生成一个.o文件,该.o文件只包含一个.c文件的内容,不是最终的可执行文件。

gcc -Wall -c -o  test2_1.o test2_1.c 
gcc -Wall -c -o  test2_2.o test2_2.c 
gcc -Wall -c -o  test2_3.o test2_3.c 

最后使用命令将3个.o文件生成一个可执行文件

gcc -o program test2_1.o test2_2.o test2_3.o

改动模块代码,不用重新编译另外两个文件,只要重新编译所改动的文件并且重新连接即可。

gcc -Wall -c -o test2_1.o test2_1.c
gcc -o program test2_1.o  test2_2.o  test2_3.o

在一个包含几百个甚至更多的文件的工程中,方法更加便利,但操作显得有些复杂。

指定查找路径

  当用户建立一个项目时,gcc将使用默认的路径,去查找和使用头文件和库文件,如在编写一个程序时,需要 在查询路径下添加一个目录,使链接器可以找到程序所需的库;编译程序时,需要查找所需的文件下,可使用-I和-L选项。

当编译程序所包含一个文件zw.h,不在默认查询目录下,

gcc -Wall -I/usr/include/zw -o test test.c

预处理就能找到程序所需要的zw.h文件

当连接X11库时,使用相同的方法处理库路径,但注意告诉链接器库的名字,命令:

gcc -L/usr/X11R6/lib -Wall test test.c -1X11

连接库

在程序中连接库,可使用-l选项,该库可为静态,或共享的。该选项只能在编译中连接的最后阶段才能指定(前面要修改Makefile文件,使它在链接的最后阶段包含库),它把所有的.o文件连接起来,若连接数学库

gcc -o test test3a.o  test3b.o  test3c.o -lm

可直接把.c源文件编译成最终的可执行问价,命令如下:

gcc -Wall -o test test.c -lm

使用管道

管道实现的是使管道前的输出成为管道后的输入。通过使用管道,可同时调用多个程序,一个程序的输出作为输入直接送给另外一个程序,而且还可以一直连续下去,不需要临时文件。

管道编译过程由gcc的-pipe选项决定,使用这个选项,gcc能建立适当的管道,如下

gcc -pipe -Wall -O3 test test.c

当编译大程序时,可节省时间。

gcc编译流程

C预处理器cpp

用来完成宏的求值,条件编译以及其他一些需要在代码传递到编译器前完成的工作。一般"#"号后面的语句由cpp来进行处理,来看下面一段代码

#define FOO (5*2)
...
printf("%d\n",FOO*2);
...

经过cpp预处理后,代码变成下面的形式:

printf("%d\n",(5*2)*2);

cpp解释宏、处理包含文件、处理#if和#ifdef声明,还有其他以#开头的标志。命令行中使用gcc -E调用cpp,通常gcc会自动调用cpp;

GUN链接器Ld

编写一个较大的程序时,经常把它分成许多独立的模块,需要连接器把所有的模块组合起来,并结合C函数库和初始化代码,产生可执行文件

通常情况下,ld被编译器gcc所调用,产生可执行代码,但是若想要更好地控制连接过程,最好手工调用ld。

GUN汇编器as

使用gcc编译程序时,产生汇编代码,as会处理这些汇编代码,从而产生目标文件(二进制文件),而目标文件将生成.o文件、库或者最终的可执行文件。as像前两项一样,通常情况下是被gcc调用,但是要使用汇编语言编写程序,可手动调用。

文件处理器ar

使用ar程序建立静态库,把几个小文件组合成一个大文件。建立静态库时,必须把多个.o文件组合成一个单独的.a文件

库显示ldd

一个可执行文件可能要使用一些共享库,可通过ldd工具显示它们,ldd是Libaray  Dependency Display的缩写。

ldd ./test

其他编译调试工具

C++编译器g++

GUNC++编译器g++和C编译器gcc的格式相同,完成的工作也是一样的。gcc也可编译C++代码,但是需要手动设置一些特殊选项,易出错。

g++所使用选项和gcc一样,为了区别C代码,对C++代码常使用.cxx扩展名;

g++ [-opations] [filename]

EGCS 

它集成了FORTRAN等编译器,集成了对gcc的各种改进和pgcc对Pentium的一些优化

calls

它用来调用gcc 的预处理器处理源程序文件,输出该文件里的函数调用树图。打印调用跟踪结果时,在函数后面用中括号给出函数所在文件的文件名。

main [test.c]

indent

设置indent选项可以指定如何格式化写好的源代码,使源代码产生统一的缩进格式;

gprof

将告诉程序中的每个函数被调用的次数和每个函数执行所占的时间。在编译程序是加上-pg选项,可在程序中使用gprof,它在程序每次执行时产生一个叫gmon.out的文件,gprof就使用这个文件剖析信息。

gprof <filename>

注:gprof产生的剖析数据很大,若想查看该数据,最好把输出重定向到一个文件中

f2c和p2c

前者将FORTRAN代码转换成C代码,后者把Pascal代码转换成C代码。一般转换小程序可直接使用它们,不许用到命令选项。

g++

多文件编译

asd

-bash-4.2$ ls
client.cpp  client.h  client_test.cpp
-bash-4.2$ g++ -c client.cpp

源文件

//client.h
//使用编译处理指令 为了避免头文件重复包含, 
#ifndef CLIENT_H
#define CLIENT_H

class Client{
private:
	static char ServerName;
	static int ClientNum;
public:
	static void ChangeServerName(char name);
	static int getClinetNum();
};
#endif  //CLIENT_H
//client.cpp
#include<iostream>
#include "client.h"
using namespace std;

void Client::ChangeServerName(char name){
	Client::ServerName = name;		//静态数据成员引用,注意加上“类名::”来修饰 
	Client::ClientNum++;
}

int Clinet::getClinetNum(){
	return Client::ClientNum;
}
//client_test.cpp
#include<iostream>
#include "client.h"
using namespace std;

int Client::ClientNum = 0;
char Client::ServerName = 'a';

int main(){
	Clinet c1;
	c1.ChangeServerName('a');
	cout<<c1.getClinetNum()<<endl;
} 

编译

-bash-4.2$ g++ -c client.cpp
In file included from client.cpp:1:0:
client.h: In function ‘void ChangeServerName(char)’:
client.h:5:21: error: ‘char Client::ServerName’ is private
         static char ServerName;

类的声明在头文件client.h,在client.cpp中添加了头文件之后,编译显示ChangeServerName()函数中使用私有成员?

在client.cpp中添加以下代码,通过编译,?

#include<iostream>
using namespace std;

将源文件.cpp编译成目标文件.o

-bash-4.2$ g++ -c client_test.cpp

编译主函数代码

-bash-4.2$ g++ -c client_test.cpp

将多个目标文件.o链接成可执行文件.out

-bash-4.2$ sudo g++ -o client_test client.o client_test.o -lm

执行可执行文件client_test

-bash-4.2$ ./client_test 
1
-bash-4.2$ 

gcc与g++区别

 

Makefile

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值