1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > linux操作系统下c语言编程入门--Linux下C开发工具介绍

linux操作系统下c语言编程入门--Linux下C开发工具介绍

时间:2020-01-02 04:28:51

相关推荐

linux操作系统下c语言编程入门--Linux下C开发工具介绍

linux操作系统下c语言编程入门--Linux下C开发工具介绍

Linux的发行版中包含了很多软件开发工具.它们中的很多是用于C和C++应用程序开发

的.本文介绍了在Linux下能用于C应用程序开发和调试的工具.本文的主旨是介绍如

何在Linux下使用C编译器和其他C编程工具,而非C语言编程的教程.

GNUC编译器

GNUC编译器(GCC)是一个全功能的ANSIC兼容编译器.如果你熟悉其他操作系统或硬

件平台上的一种C编译器,你将能很快地掌握GCC.本节将介绍如何使用GCC和一些

GCC编译器最常用的选项.

使用GCC

通常后跟一些选项和文件名来使用GCC编译器.gcc命令的基本用法如下:

gcc[options][filenames]

命令行选项指定的操作将在命令行上每个给出的文件上执行.下一小节将叙述一些你会最

常用到的选项.

GCC选项

GCC有超过100个的编译选项可用.这些选项中的许多你可能永远都不会用到,但一些主

要的选项将会频繁用到.很多的GCC选项包括一个以上的字符.因此你必须为每个选项

指定各自的连字符,并且就象大多数Linux命令一样你不能在一个单独的连字符后跟一

组选项.例如,下面的两个命令是不同的:

gcc-p-gtest.c

gcc-pgtest.c

第一条命令告诉GCC编译test.c时为prof命令建立剖析(profile)信息并且把调试信

息加入到可执行的文件里.第二条命令只告诉GCC为gprof命令建立剖析信息.

当你不用任何选项编译一个程序时,GCC将会建立(假定编译成功)一个名为a.out的可

执行文件.例如,下面的命令将在当前目录下产生一个叫a.out的文件:

gcctest.c

你能用-o编译选项来为将产生的可执行文件指定一个文件名来代替a.out.例如,将一

个叫count.c的C程序编译为名叫count的可执行文件,你将输入下面的命令:

gcc-ocountcount.c

------------------------------------------------------------------------------

--

注意:当你使用-o选项时,-o后面必须跟一个文件名.

------------------------------------------------------------------------------

--

GCC同样有指定编译器处理多少的编译选项.-c选项告诉GCC仅把源代码编译为目标代

码而跳过汇编和连接的步骤.这个选项使用的非常频繁因为它使得编译多个C程序时速

度更快并且更易于管理.缺省时GCC建立的目标代码文件有一个.o的扩展名.

-S编译选项告诉GCC在为C代码产生了汇编语言文件后停止编译.GCC产生的汇编语

言文件的缺省扩展名是.s.-E选项指示编译器仅对输入文件进行预处理.当这个选项

被使用时,预处理器的输出被送到标准输出而不是储存在文件里.

优化选项

当你用GCC编译C代码时,它会试着用最少的时间完成编译并且使编译后的代码易于调

试.易于调试意味着编译后的代码与源代码有同样的执行次序,编译后的代码没有经过优

化.有很多选项可用于告诉GCC在耗费更多编译时间和牺牲易调试性的基础上产生更小

更快的可执行文件.这些选项中最典型的是-O和-O2选项.

-O选项告诉GCC对源代码进行基本优化.这些优化在大多数情况下都会使程序执行的更

快.-O2选项告诉GCC产生尽可能小和尽可能快的代码.-O2选项将使编译的速度比使

用-O时慢.但通常产生的代码执行速度会更快.

除了-O和-O2优化选项外,还有一些低级选项用于产生更快的代码.这些选项非常的

特殊,而且最好只有当你完全理解这些选项将会对编译后的代码产生什么样的效果时再去

使用.这些选项的详细描述,请参考GCC的指南页,在命令行上键入mangcc.

调试和剖析选项

GCC支持数种调试和剖析选项.在这些选项里你会最常用到的是-g和-pg选项.

-g选项告诉GCC产生能被GNU调试器使用的调试信息以便调试你的程序.GCC提供了

一个很多其他C编译器里没有的特性,在GCC里你能使-g和-O(产生优化代码)联用

..这一点非常有用因为你能在与最终产品尽可能相近的情况下调试你的代码.在你同时使

用这两个选项时你必须清楚你所写的某些代码已经在优化时被GCC作了改动.关于调试

C程序的更多信息请看下一节"用gdb调试C程序".

-pg选项告诉GCC在你的程序里加入额外的代码,执行时,产生gprof用的剖析信息以

显示你的程序的耗时情况.关于gprof的更多信息请参考"gprof"一节.

用gdb调试GCC程序

Linux包含了一个叫gdb的GNU调试程序.gdb是一个用来调试C和C++程序的强力

调试器.它使你能在程序运行时观察程序的内部结构和内存的使用情况.以下是gdb所

提供的一些功能:

它使你能监视你程序中变量的值.

它使你能设置断点以使程序在指定的代码行上停止执行.

它使你能一行行的执行你的代码.

在命令行上键入gdb并按回车键就可以运行gdb了,如果一切正常的话,gdb将被启动

并且你将在屏幕上看到类似的内容:

GNUgdb5.0

Copyright2000FreeSoftwareFoundation,Inc.

GDBisfreesoftware,coveredbytheGNUGeneralPublicLicense,andyouare

welcometochangeitand/ordistributecopiesofitundercertainconditions.

Type"showcopying"toseetheconditions.

ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fordetails.

ThisGDBwasconfiguredas"i386-redhat-linux".

(gdb)

当你启动gdb后,你能在命令行上指定很多的选项.你也可以以下面的方式来运行gdb

:

gdb<fname>

当你用这种方式运行gdb,你能直接指定想要调试的程序.这将告诉gdb装入名为

fname的可执行文件.你也可以用gdb去检查一个因程序异常终止而产生的core文件,

或者与一个正在运行的程序相连.你可以参考gdb指南页或在命令行上键入gdb-h得

到一个有关这些选项的说明的简单列表.

为调试编译代码(CompilingCodeforDebugging)

为了使gdb正常工作,你必须使你的程序在编译时包含调试信息.调试信息包含你程序

里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号.gdb利用这些信

息使源代码和机器码相关联.

在编译时用-g选项打开调试选项.

gdb基本命令

gdb支持很多的命令使你能实现不同的功能.这些命令从简单的文件装入到允许你检查所

调用的堆栈内容的复杂命令,表27.1列出了你在用gdb调试时会用到的一些命令.想了

解gdb的详细使用请参考gdb的指南页.

基本gdb命令.

命令描述

file装入想要调试的可执行文件.

kill终止正在调试的程序.

list列出产生执行文件的源代码的一部分.

next执行一行源代码但不进入函数内部.

step执行一行源代码而且进入函数内部.

run执行当前被调试的程序

quit终止gdb

watch使你能监视一个变量的值而不管它何时被改变.

print显示表达式的值

break在代码里设置断点,这将使程序执行到这里时被挂起.

make使你能不退出gdb就可以重新产生可执行文件.

shell使你能不离开gdb就执行UNIXshell命令.

gdb支持很多与UNIXshell程序一样的命令编辑特征.你能象在bash或tcsh里那样

按Tab键让gdb帮你补齐一个唯一的命令,如果不唯一的话gdb会列出所有匹配的命

令.你也能用光标键上下翻动历史命令.

gdb应用举例

本节用一个实例教你一步步的用gdb调试程序.被调试的程序相当的简单,但它展示了

gdb的典型应用.

下面列出了将被调试的程序.这个程序被称为hello,它显示一个简单的问候,再用反

序将它列出.

#include<stdio.h>

staticvoidmy_print(char*);

staticvoidmy_print2(char*);

main()

{

charmy_string[]="helloworld!";

my_print(my_string);

my_print2(my_string);

}

voidmy_print(char*string)

{

printf("Thestringis%s",string);

}

voidmy_print2(char*string)

{

char*string2;

intsize,i;

size=strlen(string);

string2=(char*)malloc(size+1);

for(i=0;i<size;i++)

string2[size-i]=string[i];

string2[size+1]='';

printf("Thestringprintedbackwardis%s",string2);

}

用下面的命令编译它:

gcc-g-ohellohello.c

这个程序执行时显示如下结果:

../hello

Thestringishelloworld!

Thestringprintedbackwardis

输出的第一行是正确的,但第二行打印出的东西并不是我们所期望的.我们所设想的输出

应该是:

Thestringprintedbackwardis!dlrowolleh

由于某些原因,my_print2函数没有正常工作.让我们用gdb看看问题究竟出在哪儿,

先键入如下命令:

gdbhello

------------------------------------------------------------------------------

--

注意:记得在编译hello程序时把调试选项打开.

------------------------------------------------------------------------------

--

如果你在输入命令时忘了把要调试的程序作为参数传给gdb,你可以在gdb提示符下用

file命令来载入它:

(gdb)filehello

这个命令将载入hello可执行文件就象你在gdb命令行里装入它一样.

这时你能用gdb的run命令来运行hello了.当它在gdb里被运行后结果大约会象这

样:

(gdb)run

Startingprogram:/root/hello

Thestringishelloworld!

Thestringprintedbackwardis

Programexitedwithcode040

这个输出和在gdb外面运行的结果一样.问题是,为什么反序打印没有工作?为了找出

症结所在,我们可以在my_print2函数的for语句后设一个断点,具体的做法是在gdb

提示符下键入list命令三次,列出源代码:

(gdb)list

(gdb)list

(gdb)list

------------------------------------------------------------------------------

--

技巧:在gdb提示符下按回车健将重复上一个命令.

------------------------------------------------------------------------------

--

第一次键入list命令的输出如下:

1#include<stdio.h>

2

3staticvoidmy_print(char*);

4staticvoidmy_print2(char*);

5

6main()

7{

8charmy_string[]="helloworld!";

9my_print(my_string);

10my_print2(my_string);

如果按下回车,gdb将再执行一次list命令,给出下列输出:

11}

12

13voidmy_print(char*string)

14{

15printf("Thestringis%s",string);

16}

17

18voidmy_print2(char*string)

19{

20char*string2;

再按一次回车将列出hello程序的剩余部分:

21intsize,i;

22

23size=strlen(string);

24string2=(char*)malloc(size+1);

25for(i=0;i<size;i++)

26string2[size-i]=string[i];

27string2[size+1]='';

28

29printf("Thestringprintedbackwardis%s",string2);

30}

根据列出的源程序,你能看到要设断点的地方在第26行,在gdb命令行提示符下键入如

下命令设置断点:

(gdb)break26

gdb将作出如下的响应:

Breakpoint1at0x804857c:filehello.c,line26.

(gdb)

现在再键入run命令,将产生如下的输出:

Startingprogram:/root/hello

Thestringishelloworld!

Breakpoint1,my_print2(string=0xbffffab0"helloworld!")athello.c:26

26string2[size-i]=string[i];

你能通过设置一个观察string2[size-i]变量的值的观察点来看出错误是怎样产生的,

做法是键入:

(gdb)watchstring2[size-i]

gdb将作出如下回应:

Hardwarewatchpoint2:string2[size-i]

现在可以用next命令来一步步的执行for循环了:

(gdb)next

经过第一次循环后,gdb告诉我们string2[size-i]的值是`h`.gdb用如下的显示

来告诉你这个信息:

Hardwarewatchpoint2:string2[size-i]

Oldvalue=0'00'

Newvalue=104'h'

my_print2(string=0xbffffab0"helloworld!")athello.c:25

25for(i=0;i<size;i++)

这个值正是期望的.后来的数次循环的结果都是正确的.当i=11时,表达式

string2[size-i]的值等于`!`,size-i的值等于1,最后一个字符已经拷到新串

里了.

如果你再把循环执行下去,你会看到已经没有值分配给string2[0]了,而它是新串的

第一个字符,因为malloc函数在分配内存时把它们初始化为空(null)字符.所以

string2的第一个字符是空字符.这解释了为什么在打印string2时没有任何输出了.

现在找出了问题出在哪里,修正这个错误是很容易的.你得把代码里写入string2的第

一个字符的的偏移量改为size-1而不是size.这是因为string2的大小为12,但

起始偏移量是0,串内的字符从偏移量0到偏移量10,偏移量11为空字符保留.

改正方法非常简单.这是这种解决办法的代码:

#include<stdio.h>

staticvoidmy_print(char*);

staticvoidmy_print2(char*);

main()

{

charmy_string[]="helloworld!";

my_print(my_string);

my_print2(my_string);

}

voidmy_print(char*string)

{

printf("Thestringis%s",string);

}

voidmy_print2(char*string)

{

char*string2;

intsize,i;

size=strlen(string);

string2=(char*)malloc(size+1);

for(i=0;i<size;i++)

string2[size-1-i]=string[i];

string2[size]='';

printf("Thestringprintedbackwardis%s",string2);

}

如果程序产生了core文件,可以用gdbhellocore命令来查看程序在何处出错。如在函数

my_print2()中,如果忘记了给string2分配内存string2=(char*)malloc(size+

1);,很可能就会coredump.

另外的C编程工具

xxgdb

xxgdb是gdb的一个基于XWindow系统的图形界面.xxgdb包括了命令行版的gdb

上的所有特性.xxgdb使你能通过按按钮来执行常用的命令.设置了断点的地方也用图

形来显示.

你能在一个Xterm窗口里键入下面的命令来运行它:

xxgdb

你能用gdb里任何有效的命令行选项来初始化xxgdb.此外xxgdb也有一些特有的命

令行选项,表27.2列出了这些选项.

表27.2.xxgdb命令行选项.

选项描述

db_name指定所用调试器的名字,缺省是gdb.

db_prompt指定调试器提示符,缺省为gdb.

gdbinit指定初始化gdb的命令文件的文件名,缺省为.gdbinit.

nx告诉xxgdb不执行.gdbinit文件.

bigicon使用大图标.

calls

你可以在sunsite.unc.eduFTP站点用下面的路径:

/pub/Linux/devel/lang/c/calls.tar.Z

来取得calls,一些旧版本的LinuxCD-ROM发行版里也附带有.因为它是一个有用的

工具,我们在这里也介绍一下.如果你觉得有用的话,从BBS,FTP,或另一张CD-ROM上

弄一个拷贝.calls调用GCC的预处理器来处理给出的源程序文件,然后输出这些文件

的里的函数调用树图.

注意:在你的系统上安装calls,以超级用户身份登录后执行下面的步骤:1.解压和

untar文件.2.cd进入callsuntar后建立的子目录.3.把名叫calls的文件移动

到/usr/bin目录.4.把名叫calls.1的文件移动到目录/usr/man/man1.5.删除

/tmp/calls目录.这些步骤将把calls程序和它的指南页安装载你的系统上.

------------------------------------------------------------------------------

--

当calls打印出调用跟踪结果时,它在函数后面用中括号给出了函数所在文件的文件名:

main[hello.c]

如果函数并不是向calls给出的文件里的,calls不知道所调用的函数来自哪里,则只

显示函数的名字:

printf

calls不对递归和静态函数输出.递归函数显示成下面的样子:

fact<<<recursiveinfactorial.c>>>

静态函数象这样显示:

total[staticincalculate.c]

作为一个例子,假设用calls处理下面的程序:

#include<stdio.h>

staticvoidmy_print(char*);

staticvoidmy_print2(char*);

main()

{

charmy_string[]="helloworld!";

my_print(my_string);

my_print2(my_string);

my_print(my_string);

}

voidcount_sum()

{

inti,sum=0;

for(i=0;i<1000000;i++)

sum+=i;

}

voidmy_print(char*string)

{

count_sum();

printf("Thestringis%s",string);

}

voidmy_print2(char*string)

{

char*string2;

intsize,i,sum=0;

count_sum();

size=strlen(string);

string2=(char*)malloc(size+1);

for(i=0;i<size;i++)string2[size-1-i]=string[i];

string2[size]='';

for(i=0;i<5000000;i++)

sum+=i;

printf("Thestringprintedbackwardis%s",string2);

}

将产生如下的输出:

1__underflow[hello.c]

2main

3my_print[hello.c]

4count_sum[hello.c]

5printf

6my_print2[hello.c]

7count_sum

8strlen

9malloc

10printf

calls有很多命令行选项来设置不同的输出格式,有关这些选项的更多信息请参考calls

的指南页.方法是在命令行上键入calls-h.

calltree

calltree与calls类似,初了输出函数调用树图外,还有其它详细的信息。

可以从sunsite.unc.eduFTP站点用下面的路径

:/pub/Linux/devel/lang/c/calltree.tar.gz得到calltree.

cproto

cproto读入C源程序文件并自动为每个函数产生原型申明.用cproto可以在写程序时

为你节省大量用来定义函数原型的时间.

如果你让cproto处理下面的代码(cprotohello.c):

#include<stdio.h>

staticvoidmy_print(char*);

staticvoidmy_print2(char*);

main()

{

charmy_string[]="helloworld!";

my_print(my_string);

my_print2(my_string);

}

voidmy_print(char*string)

{

printf("Thestringis%s",string);

}

voidmy_print2(char*string)

{

char*string2;

intsize,i;

size=strlen(string);

string2=(char*)malloc(size+1);

for(i=0;i<size;i++)

string2[size-1-i]=string[i];

string2[size]='';

printf("Thestringprintedbackwardis%s",string2);

}

你将得到下面的输出:

/*hello.c*/

intmain(void);

intmy_print(char*string);

intmy_print2(char*string);

这个输出可以重定向到一个定义函数原型的包含文件里.

indent

indent实用程序是Linux里包含的另一个编程实用工具.这个工具简单的说就为你的代

码产生美观的缩进的格式.indent也有很多选项来指定如何格式化你的源代码.这些选项

的更多信息请看indent的指南页,在命令行上键入indent-h.

下面的例子是indent的缺省输出:

运行indent以前的C代码:

#include<stdio.h>

staticvoidmy_print(char*);

staticvoidmy_print2(char*);

main()

{

charmy_string[]="helloworld!";

my_print(my_string);

my_print2(my_string);

}

voidmy_print(char*string)

{

printf("Thestringis%s",string);

}

voidmy_print2(char*string)

{

char*string2;intsize,i;

size=strlen(string);

string2=(char*)malloc(size+1);

for(i=0;i<size;i++)string2[size-1-i]=string[i];

string2[size]='';

printf("Thestringprintedbackwardis%s",string2);

}

运行indent后的C代码:

#include<stdio.h>

staticvoidmy_print(char*);

staticvoidmy_print2(char*);

main()

{

charmy_string[]="helloworld!";

my_print(my_string);

my_print2(my_string);

}

void

my_print(char*string)

{

printf("Thestringis%s",string);

}

void

my_print2(char*string)

{

char*string2;

intsize,i;

size=strlen(string);

string2=(char*)malloc(size+1);

for(i=0;i<size;i++)

string2[size-1-i]=string[i];

string2[size]='';

printf("Thestringprintedbackwardis%s",string2);

}

indent并不改变代码的实质内容,而只是改变代码的外观.使它变得更可读,这永远是

一件好事.

gprof

gprof是安装在你的Linux系统的/usr/bin目录下的一个程序.它使你能剖析你的程

序从而知道程序的哪一个部分在执行时最费时间.

gprof将告诉你程序里每个函数被调用的次数和每个函数执行时所占时间的百分比.你如

果想提高你的程序性能的话这些信息非常有用.

为了在你的程序上使用gprof,你必须在编译程序时加上-pg选项.这将使程序在每次

执行时产生一个叫gmon.out的文件.gprof用这个文件产生剖析信息.

在你运行了你的程序并产生了gmon.out文件后你能用下面的命令获得剖析信息:

gprof<program_name>

参数program_name是产生gmon.out文件的程序的名字.

为了说明问题,在程序中增加了函数count_sum()以消耗CPU时间,程序如下

#include<stdio.h>

staticvoidmy_print(char*);

staticvoidmy_print2(char*);

main()

{

charmy_string[]="helloworld!";

my_print(my_string);

my_print2(my_string);

my_print(my_string);

}

voidcount_sum()

{

inti,sum=0;

for(i=0;i<1000000;i++)

sum+=i;

}

voidmy_print(char*string)

{

count_sum();

printf("Thestringis%s",string);

}

voidmy_print2(char*string)

{

char*string2;

intsize,i,sum=0;

count_sum();

size=strlen(string);

string2=(char*)malloc(size+1);

for(i=0;i<size;i++)string2[size-1-i]=string[i];

string2[size]='';

for(i=0;i<5000000;i++)

sum+=i;

printf("Thestringprintedbackwardis%s",string2);

}

$gcc-pg-ohellohello.c

$./hello

$gprofhello|more

将产生以下的输出

Flatprofile:

Eachsamplecountsas0.01seconds.

%cumulativeselfselftotal

timesecondssecondscallsus/callus/callname

69.230.090.09190000.00103333.33my_print2

30.770.130.04313333.3313333.33count_sum

0.000.130.0020.0013333.33my_print

%执行此函数所占用的时间占程序总

time执行时间的百分比

cumulative累计秒数执行此函数花费的时间

seconds(包括此函数调用其它函数花费的时间)

self执行此函数花费的时间

seconds(调用其它函数花费的时间不计算在内)

calls调用次数

self每此执行此函数花费的微秒时间

us/call

total每此执行此函数加上它调用其它函数

us/call花费的微秒时间

name函数名

由以上数据可以看出,执行my_print()函数本身没花费什么时间,但是它又调用了

count_sum()函数,所以累计秒数为0.13.

技巧:gprof产生的剖析数据很大,如果你想检查这些数据的话最好把输出重定向到一个

文件里.

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。