http://advancedlinuxprogramming.com提供了本书电子版的免费下载。

1 与执行环境交互

关于参数

C语言程序的main()函数使用两个参数和执行环境交互——(int)argc(char*)argv[]。前者表示所执行命令的参数个数,后者包含了所执行命令的各个参数。(char*)argv[]的第一个元素为程序本身的位置,其后为程序的参数。

看例子:

  1. #include <stdio.h>
  2. int main(int argc, char* argv[])
  3. {
  4. printf("The name of the program is '%s'.\n", argv[0]);
  5. printf("The program was invoked with %d arguments.\n", argc);
  6. if (argc > 1) {
  7. printf("The argments are:\n");
  8. for (int i = 1; i < argc; i++) {
  9. printf(" %s\n", argv[i]);
  10. }
  11. }
  12. return 0;
  13. }

程序参数有两种:

  • 短参数-只有一个连字符、后面通常只有一个小写字母,如“-s”
  • 长参数-有两个连字符、后面通常有多个大小写字母组成的字符,如“--size”

通常,Linux程序的每一个短参数都有一个对应的长参数。

Linux对于命令行参数的选择有一份指南GNU Coding Standards

Linux在getopt.h头文件中提供了getopt_long函数可以专门处理参数列表,建议使用。原书程序示例见 P.21-23,也可以用man getopt_long查看官方文档。

标准I/O

Linux有三类标准流文件,stdin,stdout,stderr。三者在Linux中也可以用文件描述符来表示:0,1,2。通常程序的错误信息最好输出到stderr中,以免与标准输出信息相混淆。

  1. fprintf(stderr, "Error:(...)");

值得注意的是,stdout是有缓存的。只有缓存填满、程序正常退出或者stdout关闭时,才会真正输出。Linux中可以使用fflush()函数来强制清洗缓存,具体看Linux手册。

而stderr没有缓存,可以即时输出。

在Shell中可以使用“2>&1”,来实现stderr与stdout的合并。例如,

  1. $ program > out_put.file 2>&1
  2. $ program > 2>&1 | filter

程序退出代码

程序正常退出时,通常代码为0。如果遇到非零代码,则表示程序出错。

Shell中使用$?表示最近执行程序的退出代码。

  1. $ ls -s /
  2. total 64
  3. 4 bin 0 dev 4 home 4 lib64 4 mnt 0 proc 4 run 4 srv 4 tmp 4 var
  4. 4 boot 4 etc 4 lib 4 media 4 opt 4 root 4 sbin 0 sys 4 usr
  5. $echo $?
  6. 0

环境变量

Linux使用大写字母表示环境变量。例如USER表示用户名,HOME表示home目录位置。在Shell中,可以使用export来改变环境变量:export USER=your_name。在C语言中,使用stdlib.h定义的setenvgetenv函数来获取或者修改环境变量。

使用临时文件

程序运行时往往需要新建临时文件来存储数据或传输数据。在Linux系统中,临时文件存放在/tmp目录下。使用临时文件需要注意以下几点:

  1. 同一程序往往会同时运行,可能是同一用户所为,也可能是不同的用户。应当确保这些程序实例拥有不同的临时文件目录,以免发生冲突。
  2. 临时文件的权限应当设定为未授权用户不得修改或替换临时文件。
  3. 临时文件的文件名不能有固定模式,不应当被预测到;否则会被恶意程序利用。

Linux提供了mkstemp函数和tmpfile函数来处理临时文件。原书 P.28-29 展示了mkstemp函数的使用案例。

2 防御性的代码

使用assert

assert主要用来检查运行时错误,其参数为一个boolean表达式。如果表达式为假,就会打印错误信息。

assert宏会大幅降低程序性能。这个可以在头文件中使用NDEBUG宏来编译规避。

  1. int func(argv);
  2. void test1() {
  3. assert(func(argv1)==0);
  4. assert(func(argv2)==0);
  5. }
  6. void test2() {
  7. int status = func(argv3);
  8. assert(status == 0);
  9. }

最好在下列情况下使用assert宏:

  1. 检查指针是否为空。assert(pointer != NULL)所产生的错误信息是这样的——Assertion 'pointer != ((void *)0)' failed.,相比于普通运行时产生的Segmentation fault (core dumped)来说要有用的多。
  2. 检查函数参数是否合法。比如函数foo()的参数arg只能为正,可以在foo()函数体内添加表达式assert(arg > 0)。从而可以检测出函数是否会错误的使用,也可以提高代码的可读性。

系统调用失败

程序的系统调用往往会在下列情况中失效:

  1. 系统所分配的资源耗尽。比如程序索取很多内存空间,向磁盘中写入内容太多,或同时打开太多文件。
  2. 当程序要求权限之外的系统调用时,Linux会屏蔽该调用。
  3. 如果用户提供了非法参数或者程序本身有bug,系统调用都会失败。
  4. 程序之外的原因,例如硬件问题,也会导致系统调用失败。
  5. 系统调用也会被诸如信号中断之类的事件所打断。这种情况下,重启系统调用即可。

系统调用的错误代码

大多数情况下,系统调用会返回0值表示成功,返回非零值表示出错。当然也有例外,比如malloc正常的会返回非零值表示指针地址,返回NULL指针则表示出错。因此,应该仔细查看Linux手册,确认系统调用的定义。

大多数系统调用会在失败时向变量errno中存入更多的错误信息。每一个系统调用失败都会覆写变量errno。因此,一旦发生失败,就应该立即转存errno中的信息。这些错误信息都是整数,其可能值都定义在预处理器中。如果要使用变量errno,应当引入<error.h>文件。、、

Linux在<string.h>提供更方便的函数strerror。该函数返回一条字符形式的errno中的错误信息。Linux还在<stdio.h>提供函数perror,能够直接将错误输出到stderr流中。

下列代码展示了strerror的用法:当文件打开失败时向stderr输出错误信息。

  1. fd = open("text.txt",O_RDONLY);
  2. if (fd == -1) {
  3. /* 打开失败 */
  4. fprintf(stderr, "Error opening file: %s\n", strerror(errno));
  5. return -1;
  6. }

一个比较特殊的错误信息是EINTR。在诸如readselectsleep这样的阻塞函数运行时,如果程序接收到中断请求,就会停止这些函数的运行,同时errno设定为EINTR

P.34页代码展示了如何使用case-switch结构处理errno值。

处理系统调用失败

通常,程序遇到系统调用失败后,最好先停止当前的任务,但不要终止程序。因为所遇到的错误也许是可恢复的。一个可行的办法是,从当前函数返回,并传递返回码给调用者指出错误所在。如果在出错之前已经分配了资源,那么这些资源应当释放掉,以免发生内存泄漏。

举个例子,一个函数要将文件内容读取到缓存中,需要下列几步:

  1. 分配缓存空间;
  2. 打开文件;
  3. 读取文件到缓存;
  4. 关闭文件;
  5. 返回缓存指针。

如果文件不存在,第2步就会失败,函数多返回返回NULL指针。但是此时缓存空间已经分配好了,如果不回收就会发生内存泄漏。如果第3步失败了,不但要回收缓存空间,还要关闭文件,释放描述符。原书P.35-36展示了相关代码。

3 编写并使用库函数文件

程序的运行离不开库函数文件。程序可以静态地或者动态地链接到库函数文件。静态链接会导致程序更大,更难升级,但容易部署;动态链接则相反。

静态库

静态库,或者叫归档(archive),就是把一组对象文件合起来存储在同一个文件中。当把代码和归档文件链接起来时,代码会在归档文件中搜索所需要的对象文件,解压缩,然后将对象文件与代码链接起来。归档文件通常使用.a后缀名,对象文件则是.o后缀名。下面代码将test1.o、test2.o两个对象文件归档在libtest.a中。

  1. $ ar cr libtest.a test1.o test2.o # cr告诉ar创建归档文件

共享库

共享库,又叫共享对象、动态链接库,和静态库类似,也是一组对象文件的集合,以.so为后缀名。但不同的是,当共享库链接到代码中编译成为可执行文件时,其中并不包含共享库中的代码,而仅仅包含一指向共享库的索引(refrence)。共享库也不包含所有代码,而仅仅包含所需要的代码。

要创建共享库文件,得先编译出对象文件:

  1. $ gcc -c -fPIC test1.c

然后把对象文件合并为共享库文件:

  1. $ gcc -shared -fPIC -o libtest.so test1.o test2.o

程序在运行时,会在/lib/usr/lib两个文件夹里搜索共享库。如果共享库文件不在这两个位置,就会出错。一个解决办法是:在gcc中使用-Wl,-rpath,/dir选项将/dir目录设定为搜索目录。另一个办法是:设置LD_LIBRARY_PATH环境变量。例如echo LD_LIBRARY_PATH=/dir1:/dir2,那么/dir1/dir2两个目录就会在两个标准目录之前搜索。注意,目录之间用冒号:分割。

另外参考:第一章 C语言 —— 静态库和动态库

标准库

即使没有任何显式的指明,gcc仍然会将所有程序与C语言的标准库libc文件相链接。如果代码中用到了数学计算函数,就应该用-lm选项链接C语言的数学标准库文件libm。

动态加卸载库文件

<dlfcn.h>头文件中包含了一组函数允许程序在运行过程中动态地加载或卸载库文件。

  1. // 打开库文件
  2. void* handle = dlopen("libtest.so", RD_LAZY);
  3. // 新建函数指针指向库函数
  4. void (*test)() = dlsym(handle, "my_func");
  5. // 运行库函数
  6. (*test)();
  7. // 关闭库文件
  8. dlclose(handle);

编写出色的GNU/Linux程序的更多相关文章

  1. 闲来瞎扯 -- 在vs2008下编写linux程序

    虽说vim很强大,但是个人感觉器代码提示功能不如visual assist来的强大.如何使用visual assist来实现代码的提示功能呢? 首先说明我的环境 : 宿主机是xp(O(∩_∩)O~还是 ...

  2. (转)完全用GNU/Linux工作 by 王珢

    完全用GNU/Linux工作 王珢      (看完这篇博文,非常喜欢王珢的这篇博客,也我坚定了学gnu/linux的决心,并努力去按照国外的计算机思维模式去学习编程提高自己.看完这篇文章令我热血沸腾 ...

  3. Gnu/Linux的学习探索

    1.Gnu/Linux是一个基于POSIX和UNIX的多用户多任务 支持多线程多CPU的类UNIX的操作系统. 继承了UNIX以网络为核心的设计思想 是性能稳定的多用户网络操作系统. 1991年10月 ...

  4. 完全用 GNU/Linux 工作(转)

    转自:http://www.chinaunix.net/old_jh/4/16102.html 看到一半,实在太长,但已觉得很好,转来分享一下. 完全用 GNU/Linux 工作 - 摈弃 Windo ...

  5. 应用 Valgrind 发现 Linux 程序的内存问题

    如何定位应用程序开发中的内存问题,一直是 inux 应用程序开发中的瓶颈所在.有一款非常优秀的 linux 下开源的内存问题检测工具:valgrind,能够极大的帮助你解决上述问题.掌握 valgri ...

  6. 在GNU/Linux下使用Lilypond排版简谱

    尽管GNU/Linux并非无所不能,但确实能在很多时候提供免费.开放的解决方案.这两天我想做一个简谱,在网上搜索乐谱排版软件,发现了基于GPL协议的Lilypond软件.只不过Lilypond是用来做 ...

  7. 我在GNU/Linux下使用的桌面环境工具组合

    为了使GNU/Linux桌面环境下加载的程序较少以节省内存资源和提高启动时间,我目前并不使用重量级的桌面环境KDE和Gnome,甚至连登录窗界面gdm或xdm都不用,而是直接启动到控制台,登录后调用s ...

  8. 应用 Valgrind 发现 Linux 程序的内存问题(转)

    Valgrind 概述 体系结构 Valgrind 是一套Linux下,开放源代码(GPL V2)的仿真调试工具的集合.Valgrind由内核(core)以及基于内核的其他调试工具组成.内核类似于一个 ...

  9. GNU/Linux与开源文化的那些人和事

     一.计算机的发明 世上本无路,走的人多了,就有了路.世上本无计算机,琢磨的人多了--没有计算机,一切无从谈起. 三个人对计算机的发明功不可没,居功至伟.阿兰·图灵(Alan Mathison Tur ...

随机推荐

  1. [家里蹲大学数学杂志]第235期$L^p$ 调和函数恒为零

    设 $u$ 是 $\bbR^n$ 上的调和函数, 且 $$\bex \sen{u}_{L^p}=\sex{\int_{\bbR^n}|u(y)|^p\rd y}^{1/p}<\infty. \e ...

  2. c#深拷贝

    /// <summary> /// 对象拷贝 /// </summary> /// <param name="obj">被复制对象</pa ...

  3. UNIX网络编程-基本API介绍(一)

    1.基本结构 大多数套接口函数都需要一个指向套接口地址结构的指针作为参数.每个协议族都定义它自己的套接口地址结构.这些结构的名字均以“sockaddr_”开头,并以对应每个协议族的唯一后缀结束. 1. ...

  4. 使用AJAX完成用户名是否存在异步校验

    一.JSP代码: 1.事件触发:onblur 2.编写AJAX代码:向Action中提交,传递username参数 <script> function checkUsername(){ / ...

  5. window下乌龟git安装和使用

    一.安装git for windows 首先下载git for windows客户端http://msysgit.github.io/ 安装过程没什么特别的,不停next就ok了 图太多就不继续了~~ ...

  6. No.016 3Sum Closest

    16. 3Sum Closest Total Accepted: 86565 Total Submissions: 291260 Difficulty: Medium Given an array S ...

  7. mongodb查询返回内嵌符合条件的文档

    db.T_Forum_Thread.find({ "ThreadReply.ReplyContent" : /范甘迪/ }, { "ThreadReply.$" ...

  8. IT在线笔试总结(一)

    综述:IT公司的技术类笔试,主要考察: (1)知识面的广度.主要考察一些业内通用性的知识,以及某一职务所必须具备的业务知识. (2)智力测试.主要考察记忆力,思维能力和学习新知识的能力. (3)技能测 ...

  9. mybaits中xml文件大于号和小于号的处理方法

    1.转义字符 原符号 < <= > >= & ' " 替换符号 < <= > >= & &apos; " 2 ...

  10. {CSDN}{英雄会}{砍树、石子游戏}

    砍树 思路: 可以将题目意图转化为:给定一棵树,求其中最接近总权值一半的子树. DFS求每个节点的所有子节点的权值和,遍历每个节点,最接近总权值一半的即为答案.复杂度O(N). 石子游戏: 思路: 一 ...