第七讲 linux下 C语言编程 --基础知识
源程序的编译
在 Linux下面,如果要编译一个 C语言源程序,我们要使用 GNU的 gcc编译器,下面我们以一个实例来说明如何使用 gcc编译器,
开放、自由和灵活是 Linux的魅力所在,而这一点在 GCC上的体现就是程序员通过它能够更好地控制整个编译过程。在使用
GCC编译程序时,编译过程可以被细分为四个阶段:
◆ 预处理( Pre-Processing)
◆ 编译( Compiling)
◆ 汇编( Assembling)
◆ 链接( Linking)
GCC起步
在学习使用 GCC之前,下面的这个例子能够帮助用户迅速理解 GCC的工作原理,并将其立即运用到实际的项目开发中去。首先用熟悉的编辑器输入清单 1所示的代码:
清单 1,hello.c
#include,stdio.h”
int main(void)
{
printf ("Hello world,Linux programming!\\n");
return 0;
}
然后执行下面的命令编译和运行这段程序:
# gcc hello.c -o hello
#,/hello
Hello world,Linux programming!
从程序员的角度看,只需简单地执行一条
GCC命令就可以了,但从编译器的角度来看,却需要完成一系列非常繁杂的工作。
首先,GCC需要调用预处理程序 cpp,由它负责展开在源文件中定义的宏,并向其中插入,#include”语句所包含的内容;接着,GCC会调用 ccl和 as将处理后的源代码编译成目标代码;最后,GCC会调用链接程序 ld,把生成的目标代码链接成一个可执行程序。
为了更好地理解 GCC的工作过程,可以把上述编译过程分成几个步骤单独进行,并观察每步的运行结果。第一步是进行预编译,使用 -E参数可以让 GCC在预处理结束后停止编译过程:
# gcc -E hello.c -o hello.i
此时若查看 hello.cpp文件中的内容,会发现
stdio.h的内容确实都插到文件里去了,而其它应当被预处理的宏定义也都做了相应的处理。
下一步是将 hello.i编译为目标代码,这可以通过使用 -c参数来完成:
# gcc -c hello.i -o hello.o
GCC默认将,i文件看成是预处理后的 C语言源代码,因此上述命令将自动跳过预处理步骤而开始执行编译过程,也可以使用 -x参数让 GCC从指定的步骤开始编译。
最后一步是将生成的目标文件链接成可执行文件:
# gcc hello.o -o hello
gcc 编译器就会为我们生成一个 hello的可执行文件,执行,/hello就可以看到程序的输出结果了,命令行中 gcc表示我们是用 gcc
来编译我们的源程序,-o 选项表示我们要求编译器给我们输出的可执行文件名为
hello 而 hello.c是我们的源程序文件,
gcc编译器有许多选项,一般来说我们只要知道其中的几个就够了,-o选项我们已经知道了,表示我们要求输出的可执行文件名,-c选项表示我们只要求编译器输出目标代码,而不必要输出可执行文件,-g选项表示我们要求编译器在编译的时候提供我们以后对程序进行调试的信息,
知道了这三个选项,我们就可以编译我们自己所写的简单的源程序了,如果你想要知道更多的选项,可以查看 gcc的帮助文档,那里有着许多对其它选项的详细说明,
2.Makefile的编写假设我们有下面这样的一个程序,源代码如下,
/* main.c */
#include "mytool1.h"
#include "mytool2.h"
int main(int argc,char **argv)
{
mytool1_print("hello");
mytool2_print("hello");
}
/* mytool1.h */
#ifndef _MYTOOL_1_H
#define _MYTOOL_1_H
void mytool1_print(char *print_str);
#endif
/* mytool1.c */
#include "mytool1.h"
void mytool1_print(char *print_str)
{
printf("This is mytool1 print %s\n",print_str);
}
/* mytool2.h */
#ifndef _MYTOOL_2_H
#define _MYTOOL_2_H
void mytool2_print(char *print_str);
#endif
/* mytool2.c */
#include "mytool2.h"
void mytool2_print(char *print_str)
{
printf("This is mytool2 print %s\n",pri
nt_str);
}
当然由于这个程序是很短的我们可以这样来编译
gcc -c main.c
gcc -c mytool1.c
gcc -c mytool2.c
gcc -
o main main.o mytool1.o mytool2.o
这样的话我们也可以产生 main程序,而且也不是很麻烦,但是如果我们考虑一下如果有一天我们修改了其中的一个文件 (比如说
mytool1.c)那么我们难道还要重新输入上面的命令?也许你会说,这个很容易解决啊,
我写一个 SHELL脚本完成,是的对于这个程序来说,是可以起到作用的,但是当我们把事情想的更复杂一点,如果我们的程序有几百个源程序的时候,难道也要编译器重新一个一个的去编译?
我们只要执行以下 make,就可以把上面的问题解决掉,在我们执行 make之前,我们要先编写一个非常重要的文件,--Makefile.对于上面的那个程序来说,可能的一个 Makefile
的文件是,
# 这是上面那个程序的 Makefile文件
main:main.o mytool1.o mytool2.o
gcc -o main main.o mytool1.o mytool2.o
main.o:main.c mytool1.h mytool2.h
gcc -c main.c
mytool1.o:mytool1.c mytool1.h
gcc -c mytool1.c
mytool2.o:mytool2.c mytool2.h
gcc -c mytool2.c
有了这个 Makefile文件,不管我们什么时候修改了源程序当中的什么文件,我们只要执行 make命令,我们的编译器都只会去编译和我们修改的文件有关的文件,
在 Makefile中以 #开始的行都是注释行
Makefile中最重要的是描述文件的依赖关系的说明,
一般的格式是,
target,components
TAB rule
第一行表示的是依赖关系,第二行是规则,
比如说我们上面的那个 Makefile文件的第二行
main:main.o mytool1.o mytool2.o
表示我们的目标 (target)main的依赖对象
(components)是 main.o mytool1.o mytool2.o
当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令,就象我们的上面那个 Makefile
第三行所说的一样要执行
gcc -o main main.o mytool1.o mytool2.o
注意规则一行中的 TAB表示那里是一个 TAB键
Makefile有三个非常有用的变量,
分别是 $@,$^,$<代表的意义分别是,
$@--目标文件,
$^--所有的依赖文件,
$<--第一个依赖文件,
如果我们使用上面三个变量,那么我们可以简化我们的 Makefile文件为,
# 这是简化后的 Makefile
main:main.o mytool1.o mytool2.o
gcc -o $@ $^
main.o:main.c mytool1.h mytool2.h
gcc -c $<
mytool1.o:mytool1.c mytool1.h
gcc -c $<
mytool2.o:mytool2.c mytool2.h
gcc -c $<
3.程序库的链接试着编译下面这个程序
/* temp.c */
#include,stdio.h”
int main(int argc,char **argv)
{
double value;
printf("Value:%f\n",sin(value));
}
这个程序相当简单,但是当我们用 gcc -
o temp temp.c 编译时会出现下面所示的错误,
/tmp/cc33Kydu.o,In function `main',
/tmp/cc33Kydu.o(.text+0xe),undefined
reference to `sin'
collect2,ld returned 1 exit status
出现这个错误是因为编译器找不到 sin的具体实现,虽然我们包括了正确的头文件,但是我们在编译的时候还是要连接确定的库,在
Linux下,为了使用数学函数,我们必须和数学库连接,为此我们要加入 -lm 选项,gcc -
o temp temp.c -lm这样才能够正确的编译,
也许有人要问,前面我们用 printf函数的时候怎么没有连接库呢?是这样的,对于一些常用的函数的实现,gcc编译器会自动去连接一些常用库,这样我们就没有必要自己去指定了,有时候我们在编译程序的时候还要指定库的路径,这个时候我们要用到编译器的 -L选项指定路径,比如说我们有一个库在 /home/hoyt/mylib下,这样我们编译的时候还要加上 -L/home/hoyt/mylib.对于一些标准库来说,我们没有必要指出路径,只要它们在起缺省库的路径下就可以了,系统的缺省库的路径
/lib /usr/lib /usr/local/lib 在这三个路径下面的库,我们可以不指定路径,
库依赖
在 Linux 下开发软件时,完全不使用第三方函数库的情况是比较少见的,通常来讲都需要借助一个或多个函数库的支持才能够完成相应的功能。
从程序员的角度看,函数库实际上就是一些头文件(,h)和库文件(,so或者,a)的集合。虽然
Linux下的大多数函数都默认将头文件放到
/usr/include/目录下,而库文件则放到 /usr/lib/目录下,但并不是所有的情况都是这样。正因如此,
GCC在编译时必须有自己的办法来查找所需要的头文件和库文件。
GCC采用搜索目录的办法来查找所需要的文件,-I选项可以向 GCC的头文件搜索路径中添加新的目录。例如,如果在
/home/xiaowp/include/目录下有编译时所需要的头文件,为了让 GCC能够顺利地找到它们,就可以使用 -I选项:
# gcc foo.c -I /home/xiaowp/include -o foo
同样,如果使用了不在标准位置的库文件,
那么可以通过 -L选项向 GCC的库文件搜索路径中添加新的目录。例如,如果在
/home/xiaowp/lib/目录下有链接时所需要的库文件 libfoo.so,为了让 GCC能够顺利地找到它,可以使用下面的命令:
# gcc foo.c -L /home/xiaowp/lib -lfoo -o foo
值得好好解释一下的是 -l选项,它指示 GCC
去连接库文件 libfoo.so。 Linux下的库文件在命名时有一个约定,那就是应该以 lib三个字母开头,由于所有的库文件都遵循了同样的规范,因此在用 -l选项指定链接的库文件名时可以省去 lib三个字母,也就是说
GCC在对 -lfoo进行处理时,会自动去链接名为 libfoo.so的文件。
4.程序的调试我们编写的程序不太可能一次性就会成功的,在我们的程序当中,会出现许许多多我们想不到的错误,这个时候我们就要对我们的程序进行调试了,
最常用的调试软件是 gdb.如果你想在图形界面下调试程序,那么你现在可以选择
xxgdb.记得要在编译的时候加入 -g选项,关于 gdb的使用可以看 gdb的帮助文件,
用 gdb 调试 GCC 程序
Linux 包含了一个叫 gdb 的 GNU 调试程序。 gdb 是一个用来调试 C 和 C++ 程序的强力调试器。 它使你能在程序运行时观察程序的内部结构和内存的使用情况。 以下是 gdb 所提供的一些功能:
· 它使你能监视你程序中变量的值。
· 它使你能设置断点以使程序在指定的代码行上停止执行。
· 它使你能一行行的执行你的代码。
gdb 基本命令
gdb 支持很多的命令使你能实现不同的功能。 这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令,表
27.1列出了你在用 gdb 调试时会用到的一些命令。 想了解 gdb 的详细使用请参考
gdb 的指南页。
基本命令
file 装入想要调试的可执行文件。
kill 终止正在调试的程序。
list 执行一行源代码但不进入函数内部。
next执行下一行源代码但不进入函数内部。
step执行一行源代码而且进入函数内部。
run执行当前被调试的程序
quit终止
gdbwatch 使你能监视一个变量的值而不管它何时被改变。
break 在代码里设置断点,这将使程序执行到这里时被挂起。
make 使你能不退出 gdb 就可以重新产生可执行文件。
shell 使你能不离开 gdb 就执行 UNIX shell 命令。
SHELL 编程技术
通常情况下,我们从命令行输入命令每输入一次就能够得到系统的一次响应。一旦需要我们一个接着一个的输入命令而最后才得到结果的时候,这样的做法显然就没有效率。要达到这样的目的,通常我们利用 shell程序或者 shell脚本来实现。
一、简介
Shell编程有很多类似 C语言和其他程序语言的特征,但是又没有编程语言那样复杂。 Shell程序就是放在一个文件中的一系列 Linux命令和实用程序,在执行的时候,
通过 Linux一个接着一个地解释和执行每个命令。
象编写高级语言的程序一样,编写一个 shell
程序需要一个文本编辑器,如 VI等,
在文本编辑环境下,依据 shell的语法规则,输入一些 shell/linux命令行,形成一个完整的程序文件,
执行 shell程序文件有三种方法
(1)#chmod +x file(在 /etc/profile中,加入 export
PATH=${PATH}:
~/yourpath,就可以在命令行下直接运行,像执行普通命令一样 )
(2)#sh file
(3)#,file
(4)#source file
在编写 shell时,第一行一定要指明系统需要那种
shell解释你的 shell程序,如,#! /bin/bash,
(2)定义变量
shell语言是非类型的解释型语言,不象用
C++/JAVA语言编程时需要事先声明变量,给一个变量赋值,实际上就是定义了变量,
在 linux支持的所有 shell中,都可以用赋值符号 (=)为变量赋值,
如,
abc=9 (bash/pdksh不能在等号两侧留下空格 )
由于 shell程序的变量是无类型的,所以用户可以使用同一个变量时而存放字符时而存放整数,
如,
name=abc (bash/pdksh)
在变量赋值之后,只需在变量前面加一个 $去引用,
如,
echo $abc
shell中引号的使用方法
shell使用引号 (单引号 /双引号 )和反斜线 ("\")用于向 shell解释器屏蔽一些特殊字符,
反引号 (")对 shell则有特殊意义,
如,
abc="how are you" (bash/pdksh)
这个命令行把三个单词组成的字符串 how are you
作为一个整体赋值给变量 abc,
abc1='@LOGNAME,how are you!' (bash/pdksh)
abc2="$LOGNAME,how are you!" (bash/pdksh)
LOGNAME变量是保存当前用户名的 shell变量,假设他的当前值是,wang.执行完两条命令后,
abc1的内容是,$LOGNAME,how are you!.而
abc2的内容是 ;wang,how are you!,
象单引号一样,反斜线也能屏蔽所有特殊字符,但是它一次只能屏蔽一个字符,而不能屏蔽一组字符,
反引号的功能不同于以上的三种符号,它不具有屏蔽特殊字符的功能,但是可以通过它将一个命令的运行结果传递给另外一个命令,
下面我们来看一个简单的 shell程序:
1、首先建立一个内容如下的文件,名字为 date,将其存放在目录下的 bin子目录中。
#Program date
#usageto,:show the date in this way
(注释 )
echo,Mr.$USER,how are you:”
echo,Wish you a lucky day !”
2、编辑完该文件之后它还不能执行,我们需要给它设置可执行权限。使用如下命令:
chmod +x date
通过以上过程之后,我们就可以像使用 ls命令一样执行这个 shell程序,
[beichen@localhost bin]$ date
Mr.beichen,how are you:
Wish you a lucky day !
为了在任何目录里都可以执行这个程序,
可以将 bin的这个目录添加到路径中去。
[beichen@localhost bin]$
PATH=$PATH:$HOME/bin
(注:这里的 $HOME代替的是
/home/beichen,而 bin目录是自己建的一个目录 )
另外一种执行 date的方法就是把它作为一个参数传给 shell命令,
[beichen@localhost /]$ bash date
Mr.beichen,how are you:
Wish you a lucky day !
二,shell参数
如同 ls 命令可以接受目录等作为它的参数一样,在 shell编程时同样可以使用参数。 Shell有位置参数和内部参数。
1,位置参数
由系统提供的参数称为位置参数。位置参数的值可以用 $N得到,N是一个数字,
如果为 1,即 $1.类似 C语言中的数组,
Linux会把输入的命令字符串分段并给每段进行标号,标号从 0开始。第 0号为程序名字,从 1开始就表示传递给程序的参数。如
$0表示程序的名字,$1表示传递给程序的第一个参数,以此类推。
2,内部参数
上述过程中的 $0是一个内部变量,它是必须的,而 $1则可有可无。和 $0一样的内部变量还有以下几个。
$# ----传递给程序的总的参数数目
$? ----上一个代码或者 shell程序在 shell中退出的情况,如果正常退出则返回 0,反之为非 0值。
$* ----传递给程序的所有参数组成的字符串。
建立一个内容为如下的程序 P1:
echo,Program name is $0”
echo,There are totally $# parameters
passed to this program”
echo,The last is $?”
echo,The parameters are $*”
执行后的结果如下:
[beichen@localhost bin]$ P1 this is a test program
//传递 5个参数
Program name is /home/beichen/bin/P1
//给出程序的完整路径和名字
There are totally 5 parameters passed to this program
//参数的总数
The last is 0
//程序执行结果
The parameters are this is a test program
//返回由参数组成的字符串
下面我们利用内部变量和位置参数编写一个名为 del的简单删除程序:
#name,del
#author,liangnian
#this program to compress a file to the dustbin
if test $# -eq 0
then
echo,Please specify a file!”
else
gzip $1 //先对文件进行压缩
mv $1.gz $HOME/dustbin //移动到回收站
echo,File $1 is deleted !”
fi
三、变量表达式
在上面我们编写的小程序中我们用到了一个关键字 test,其实它是 shell程序中的一个表达式,比较 (test)。通过和 shell提供的
if等条件语句相结合我们可以方便的完成判断。
其用法如下:
test 表达式
表达式所代表的操作符有字符串操作符、数字操作符、逻辑操作符以及文件操作符。其中文件操作符是一种 shell独特的操作符,因为 shell里的变量都是字符串,
为了达到对文件进行操作的目的,于是才提供了这样的一种操作符。
1,字符串比较
作用:测试字符串是否相等、长度是否为零,
字符串是否为 NULL(注,bash区分零长度字符串和空字符串 )
常用的字符串操作符有:
= 比较两个字符串是否相同,同则为“是”
!= 比较两个字符串是否相同,不同则为“是”
-n 比较字符串长度是否大于零,如果大于零
则为“是”
-z 比较字符串的长度是否等于零,如果等于则为“是”
2,数字比较
这里区别于其他编程语言,test语句不使用 >
类似的符号来表达大小的比较,而是用整数式来表示这些。
-eq 相等
-ge 大于等于
-le 小于等于
-ne 不等于
-gt 大于
-lt 小于
3,逻辑操作
! 反:与一个逻辑值相反的逻辑值
-a 与 (and):两个逻辑值为“是”返回值才为“是”,反之为“否”
-o 或 (or):两个逻辑值有一个为“是”,返回值就为“是”
4,文件操作
文件测试表达式通常是为了测试文件的信息,一般由脚本来决定文件是否应该备份、复制或删除。由于 test关于文件的操作符有很多,我们只列举一些常用的。
-d 对象存在且为目录返回值为“是”
-f 对象存在且为文件返回值为“是”
-L 对象存在且为符号连接返回值为
“是”
-r 对象存在且可读则返回值为“是”
-s 对象存在且长度非零则返回值为
“是”
-w 对象存在且可写则返回值为“是”
-x 对象存在且可执行则返回值为“是”
四、循环结构语句
shell常见的循环语句有 for循环,while
循环,until循环
for 循环
语法,for 变量 in 列表
do
操作
done
注:变量是要在循环内部用来指代当前所指代的列表中的那个对象的。
列表是在 for 循环的内部要操作的对象,可以是字符串也可以是文件,如果是文件则为文件名。
例:删除垃圾箱中的所有,gz文件
#delete all file with extension of,gz” in
the dustbin
for I in $HOME/dustbin/*.gz
do
rm $i
echo,$i has been deleted!”
done
执行结果如下:
[beichen@localhost bin]$.f_rmgz
/home/beichen/dustbin/nessus-
4.0.0.2.tar.gz has been deleted!
/home/beichen/dustbin/gftp-
2.2.1.tar.gz has been deleted!
While循环
语法,while 表达式
do
操作
done
只要 while表达式成立,do和 done之间的操作就一直会进行。
until循环
语法,until 表达式
do
操作
done
重复 do和 done之间的操作直到表达式成立为止。
例,
#test until
#add from 1 to 100
total=0
num=0
until test num -eq 100
do
total=`expr $total + $num`
//注意,这里的引号是反引号,下同
num=`expr $num+1`
done
echo,The result is $total”
执行结果如下:
[beichen@localhost bin]$until
The result is 5050!
五、条件语句
Shell程序中的条件语句主要有 if语句、
case语句;
If语句
语法,if 表达式 1 then
操作
elif 表达式 2 then
操作
elif 表达式 3 then
操作
…..
else
操作
fi
Linux里的 if的结束标志是将 if反过来写成 fi;
而 elif其实是 else if的缩写
其中 elif理论上可以有无限多个。
Case语句
语法,case 字符串 in
值 1|值 2)
操作,:
值 3|值 4)
操作,:
值 5|值 6)
操作,:
*}
操作,:
esac
case的作用就是当字符串与某个值相同是就执行那个值后面的操作。如果同一个操作对于多个值,则使用” |”将各个值分开。
在 case的每一个操作的最后面都有两个”,:”,分号是必须的。
例:
case $USER in
beichen)
Echo,You are beichen!”;;
liangnian)
echo,You are liangnian”; //注意这里只有一个分号
echo,Welcome!”;; //这里才是两个分号
root)
echo,You are root!:echo Welcome!”;; //将两命令写在一行,用一个分号作为分隔符
*)
echo,Who are you?$USER?”;;
esac
执行结果:
[liangnian@localhost bin]$ test
You are liangnian
Welcome!