<!-- @page { margin: 2cm } P { margin-bottom: 0.21cm } -->
>rm Untitled/ 1.odt
上面命令是对 'Untitled 1.odt' 执行删除。需要对空格进行转义。
make 工具可以用来跟踪那些更新过的模块,并确保在编译时使用所有程序模块的最新版本。
CVS ,并发版本控制系统是一个源代码管理系统,用来跟踪项目所涉及到的文件版本。
>apropos name
>gcc –version
>pwd
>cd ~
>mkdir example
>cd example
>vim example.c &
>cat -n example.c example.h
>cat >Makefile RETURN
example:example.c example.h RETURN
gcc -o example example.c RETURN
CONTROL+D
>make
>chmod u+x example
>./example
>touch example.c
>make
预处理指令:在编译初始阶段,C 预处理器扩展预处理指令,使程序为编译过程的后续阶段做好准备。预处理指令以'#' 开头。
利用'<>' 将头文件括起来,将指示预处理器在标准目录列表查找头文件。
可以在'-I'(i) 选项告诉预处理器在指定位置查找头文件。
>mkdir inc include output
>mv example.c inc
>mv example.h include
>rm example
>ls *
>cat >Makefile RETURN
VPATH=.:./inc:./include RETURN
example:example.c example.h RETURN
gcc -I ./include $< -o example RETURN
mv example ./output RETURN
CONTROL+D
>make
>./example
尽管大多数C 函数可以使用任何名称,但是每个程序必须有且只能有一个名为main 的函数。
编译过程分为4 部分,C 预处理器扩展宏定义并包含头文件。在编译阶段根据源文件中的指令创建汇编语言代码。然后汇编器创建计算机可读的目标文件。在编译的最后一个阶段,连接器搜索指定的函数库,找到程序使用的函数,并将这些函数的目标模块与本程序的目标代码合并在一起。默认情况下C 编译器会链接标准C 函数库libc.o ,如果希望链接到其他函数库,用户必须在命令行上使用'-l' 选项指定这些函数库。
>gcc calc.c -lm
-l 选项使用的函数库名称约定,将跟在-l(L) 后面的那个字母后面追加lib 后,并添加扩展名.so 或者.a 。本例中的代表libm.so 。使用命名约定gcc 知道在/usr/lib 和/lib 中搜索这些函数库。还可以使用'-L' 选项让gcc 搜索其他目录
>gcc pgm.c -L. -L/usr/x11R6/lib -lgraphics
该示例,gcc 在搜索/usr/lib 和/lib 目录之前,在工作目录和/usr/X11R6/lib 中搜索函数库文件libgraphics.a 。
在编译的最后一步,如果没有使用'-o' 选项指定不同的文件名,连接器将会创建一个名为a.out 的可执行文件。
ELF 格式 Linux 的二进制文件使用 ELF(Executable and Linking Format, 可执行可链接格式 ) 格式。
>pwd
>cd ~/example/src
>ls
>file example
选项'-O3' 告诉gcc 使用C 编译器优化器。优化器使得目标代码更加高效,这样可执行程序可能更快的运行。
>gcc -O3 -o example example.c
可以使用gcc 命令的'-c' 选项来抑制编译过程的连接阶段。'-c' 选项不会认为那些没有解析的外部引用是错误的,用户可以使用这一功能在创建程序模块时进行编译和语法调试。
>gcc -c -I ../include example.c
>ls
>gcc -o example example.o
使用共享库:
大多数现代操作系统都使用共享库,也称为动态库。在编译时,这些库并不会链接到程序中去而是在程序开始时( 或者在某些情况下是在程序开始后) 加载进来。共享库扩展名是.so(share object) 。
归档库 与共享库不同的是较旧的静态链接库,也称为归档库。
使用共享库的主要原因是在于减少内存用量并可增加程序的可维护性。现在他们基本已经取代了静态库,成为库类型的首选。
ldd ldd(list dynamic dependencies, 列出动态依赖关系 ) 工具告诉用户某个程序需要那些共享库。
>ldd example
执行动态运行时连接的程序 ld-linux.so 总是在 /usr/lib 目录下查找函数库。 ld 要搜索的其他目录根据ld 安装的方式而有所不同。在编译时( 实际上是连接) ,还可以通过在 -r 选项后面带上一个冒号隔开的目录列表指定搜索路径,向 ld 添加要查找的目录。搜索路径只使用绝对路径名。 '-r' 选项后面没有空格。
gcc -o gnome-session -r/lib:/usr/X11R6/lib
这个命令行允许ld.so 在标准目录/usr/lib 之外的/lib 和/usr/X11R6/lib 目录中搜索可执行文件所需要的库。
编译器需要在连接时找到共享库,从而确保头文件 (.h) 声明的所需函数和过程确实存在。使用 '-L' 选项告诉编译时连接器在目录 mylib 中搜索共享或者静态库 :-L mylib 。与'-r' 搜索路径不同,-L 选项可以使用相对路径。
命令行搜索路径是一个非常新的方法。传统上使用LD_LIBRARY_PATH( 进来使用LD_RUN_PATH) 环境变量创建搜索路径。这些变量格式与PATH 相同。
通常,要在常见的库位置之前搜索LD_LIBRARY_PATH 中的目录。使用LD_LIBRARY_PATH 带来一些问题,因为只能有一个环境变量,所以所有程序必须共享他,如果两个程序使用相同的库名称,或者使用了同一个库的不同且不兼容的版本,那么只会搜索到第一个,这样,其中一个程序就不能运行。
包装器 LD_LIBRARY_PATH 还是可以用于称为包装器的脚本中,这些脚本用于修复受损的二进制文件。假如受损的二进制文件bb 使用共享库libbb.so ,bb 程序员要求用户将这个共享库放到/opt/bb/lib 中而不是/usr/lib 中。命令ldd bb 将告诉用户缺少那些库。使用下面的方法可解决这个问题: 将bb 重命名为bb.broken ,并创建名为bb 的/bin/sh 包装器。
#! /bin/sh
LD_LIBRARY_PATH=/opt/bb/lib
export LD_LIBRARY_PATH
exec bb.broken “$@”
创建共享库: 建立动态可加载共享库并不是一项简单的事情:它涉及可重入函数 的调用,库入口例程的定义以及其他任务的执行。当用户希望创建共享目标库时,必须使用-fPIC(position-independent code, 位置无关代码) 选项最小程度地编译源文件,并使用连接器的-shared -x 选项将最终的目标文件连接到libxx.so 。
>ld -shared -x -o libmylib.so *.o
make 保持一组程序最新:当处理大型程序时,判断由于模块间依赖关系而要重新编译哪些模块是一件困难 并乏味的工作。make 工具可将这个工作自动化。
依赖行:目标文件和前提文件 在最简单的情况下,make 在工作目录下名为makefile 或者Makefile 的文件中查找依赖行。依赖行指出文件之间的关系,指定某个目标文件依赖于一个或者多个前提文件。如果用户在目标文件之前修改了它的任何一个前提文件,make 将根据依赖行后面的构造命令更新目标文件。
简单的makefile 语法如下:
target:prerequisite-list
TAB constructing-commands
依赖行由目标和前提文件列表prerequisite-list 组成,两者由冒号隔开。每行constructing-commands( 可能是多行) 必须以TAB 开头,并且必须位于依赖行的下面。
依赖行上的前提条件中的每个文件也可以是另一个依赖行的目标。用户可以将任何shell 命令放置在构造行上。makefile 的第一个目标是默认目标,当用户不带任何参数调用make 时,就会构建这个目标。
form : size.o length.o
gcc -o form size.o length.o
size.o:size.c form.h
gcc -c size.c
length.o:length.c form.h
gcc -c length.c
form.h:num.h table.h
cat num.h table.h > form.h
如果希望使用make 重新构建makefile 中除第一个目标之外的其他目标,就必须将这个目标作为参数传递给make 。
隐含的依赖关系 可以依靠隐含的依赖和构造命令来辅助完成编写makefile 的任务。
# Makefile for compute
#
compute:compute.o calc.o
gcc -o compute compute.o calc.o
compute.o:compute.c compute.h
gcc -c -O3 compute.c
calc.o:calc.c
gcc -c calc.c
clean:
rm *.o *core* *~
第3 组依赖行不是必须的,因为没有这一行make 也能推断出calc.o 依赖于calc.c 。
最后一个clean 目标没有前提文件。
在运行make 后,如果不修改任何前提文件而再次运行make ,make 将说明该程序已经是最新的,而不执行任何命令:
touch 使用touch 工具来改变前提文件的修改时间。make 工具只执行那些更新过期目标所需要的命令。
'-n' 如果用户希望查看运行make 将要执行什么,可使用-n 选项。'-n' 选项说明make 将要执行的命名但是并不是真正执行这些命令。
'-t' 用户可以使用 touch 来更新所有源文件的修改时间 ,这样make 就会认为没有文件是最新的,然后就重新编译所有文件。或者可以使用 touch 或者 make 的 '-t' 选项来更新所有目标文件的修改时间。
>touch example.c
>make -n
>make -t
>make -n
调试C 程序 没有新闻是最好的新闻 用户知道什么是最好的;调试C 程序的一种方法是在源码中的关键地方插入打印语句。
gcc 编译器警告选项:所有可使编译器行为更加严格的选项都以大写字母W 开头。
>gcc -c - -Wall example.c
-Wimplicit 没有显式地声明函数或者参数
-Wreturn-type 返回值不是空的函数。没有返回值或者函数类型默认为int
-Wunused 变量声明之后未被使用
-Wcomment 字符串/* 出现在注释中
-Wformat 某些输入/ 输出语句所包含的格式说明与参数不匹配
-Wall 选项显示上面列出的所有错误的警告信息。
如果函数没有明确定义类型,编译器默认类型为int 。如果一个程序成功执行,按照约定它应该返回0 。如果没有返回值,那么说明未定义退出代码。
有很多调试器可以用来实现简单调试方法所具备的功能。这些调试器包括gdb kgdb xxgdb ddd 等。
使用调试器可以监视和控制程序的执行。可以逐行地执行程序,此时可以检查执行环境的状态。
核心转储 调试器还允许检查核心文件。在程序执行过程中出现严重错误时,操作系统将创建核心文件,该文件包含着发生错误时该程序和系统的状态信息。这个文件由程序正在使用的计算机内存的转储构成。可以使用ulimit 内置命令使得核心文件可以被保存下来。
>ulimit -c unlimited
gdb 符号调试器 为了充分利用符号调试器来调试程序,必须使用 -g 选项来编译程序,这个选项使得 gcc 产生调试器所需要的额外信息。这些信息包括一个符号表,即程序中用到的所有变量的名称和他们相关值的列表。
使用-g 选项总是有帮助的,即使在用户发布软件时也有用。包括调试符号会使二进制文件稍微变大。调试符号不会使程序的运行速度变慢。
当用户编译程序用于调试时,将优化标志限制到 -O 或者 -O2 。 因为调试和优化有着本质的不同目标,所以最好避免将这两个操作合并。
>gcc -g example.c -o example
将优化选项完全关闭有时候可以消除错误。但是使用这种方式消除错误不应该被视为长久的解决办法。如果使用 -O 选项或者是 -O2 选项,正确的代码编译后应该可以正确的运行。 -O3 选项通常包含带有实验性质的优化,所以并不是所有情形下都能产生正确的代码。
线程 线程是进程内部的单一顺序控制流。线程是多线程的基础。在多线程程序中,一个程序可以并发控制多个运行线程,每个线程执行不同的任务。
系统调用 linux 内核的 3 个基本职能是进程控制,文件系统管理和外围设备操作。可以通过系统调用和库函数访问这些内核操作。系统调用指示系统根据用户的指令执行某些工作。库函数是间接调用,它为用户发送系统调用。库例程的优点是可以将用户从内核操作的底层细节中解脱出来,因为这些例程都经过仔细编写,确保能够高效地执行。
strace 跟踪系统调用,strace 是一调试工具,它可以显示某个进程或者程序所发出的系统调用的轨迹。因为没有必要重新编译希望追踪的程序,所以可以对那些没有源代码的二进制程序使用strace 工具。
控制进程 当用户在shell 提示符后输入命令时,shell 进程调用fork 系统调用创建自身的副本,然后使用exec 系统调用,将另一个不同的程序覆盖内存中的这个副本。
访问文件系统 当程序读取文件或者向文件中写入的时候会产生很多动作。文件位置,文件名到inode 的转换,用户的访问权限,定位文件包含文件片的所有磁盘块。
在linux 系统中,外围设备是通过文件系统接口访问的,每个外围设备被表示为一个或者多个特殊文件,通常位于/dev 目录下,当读取或则写入这些特殊文件时,内核将用户请求传递给适当的内核设备驱动程序。它允许对大量不同的设备使用相同的基本工具。
拥有标准系统调用和库例程是 linux 工具可移植的关键。