- 论坛徽章:
- 0
|
ALP(Advance Linux Programming)学习笔记 (一)
(第二章学习总结)
如何编写好的代码
一. 与环境的交互
1. 命令选项的获取:
用-x或者--x方式提供命令选项,使用getopt_long可以方便的获取标准的linux选项列表(标准列表分为short options,long options)
#include
int getopt_long(int argc, char * const argv[], const char *optstring, const struct option *longopts, int *longindex); argc和argv为main输入的参数
optstring
表示期望接收的选项的short形式,如果对应的字母后面有一个':'表示该选项后有一个需要解释的子参数。如果后面有两个冒号::表示有一个可选的子参数.一个简单
的例子:"ho:v"。表示可以接受'h','o','v'形式的短选项,并在'o'后还要接收一个子参数。
longopts表示期望接收的选项的long形式,longopts的结构如下:
struct option { const char *name; int has_arg; int *flag; int val; };
一个简单的例子:
struct option long_op = {
{"help", 0, NULL, 'h'},
{"output",1,NULL, 'o'},
....
{NULL, 0, NULL, 0}
};
longindex设为NULL
一段读取命令选项的例子程序:
#include
#include
/*get option relative value*/
const char * const short_op= "ho:v";
const struct option long_op[]={
{"help", 0, NULL, 'h'},
{"output",1,NULL, 'o'},
{"verbose",0,NULL,'v'},
{NULL, 0, NULL, 0}
};
int next_op; /*this is returned from getopt_long*/
int next_cmd;/*表示参数后面的东西的索引,比如指定的输入文件等 */
/*command process relative value*/
int verbose = 0;
const char *output_filename=NULL;
char *program_name;
void print_usage (FILE* stream, int exit_code){ fprintf (stream, "Usage: %s options [ inputfile ... ]\n" ,program_name); fprintf (stream, "-h --help Display this usage information.\n" "-o --output filename Write output to file.\n" "-v --verbose Print verbose messages.\n"); exit (exit_code);}
int main(int argc, char *argv[])
{
int fd;
int rc;
char outstr[]="OK!..."; program_name= argv[0]; do
{
next_op= getopt_long(argc,argv,short_op,long_op,NULL);
switch(next_op)
{
case 'h':
print_usage(stdout,0);
break;
case 'o':
fd = fopen(optarg,"a");
fputs(outstr,fd);
break;
case 'v':
verbose = 1;
break;
case '?':
print_usage(stderr,1);
break;
case -1: /*这里比较重要,表示没有任何新的参数了*/
next_cmd = optind;
break;
default:
abort();
} }while(next_op!=-1);
if (verbose) { int i; for (i = optind; i printf("Argument: %s\n", argv); }
return 0;
}
2.标准输入输出
在设备层,可以使用文件描述符(file descriptor)去访问三个标准设备: 0-标准输入 1-标准输出 2-标准错误输出
在文件层,可以通过三个始终打开的三个流来访问:stdin-标准输入 stdout-标准输出 stderr-标准错误输出
需要注意的是: stdout是缓冲了的,如果要及时输出,需要使用fflush的方法。但stderr是没有缓冲的,写到stderr的东西会直接送出。
3.环境变量:
PATH, USER,HOME,DISPLAY等
多数的shell会自动寻找所有的系统环境变量,并对应生成shell变量。可以通过set或者直接赋值的方式来设置shell变量。用export可以将shell变量发送到环境变量。
在程序中访问环境变量的方法是: getenv setenv unsetenv
#inlcude
char *getenv(const char *name);
int setenv(const char *name, const char *value, int overwrite);
void unsetenv(const char *name);
访问所有的环境变量的方法: 通过environ变量(定义在GNU C库中,类型为char **),这个变量实际上是进程产生的时候由父进程将当前的环境变量复制了一份,并用environ指向它(这里environ是一个字符串数组的指针,也就是每个环境变量是一个字符串)
4. 使用临时文件
/tmp是linux存放临时文件的地方(一般情况下,该目录实际上是tmpfs文件系统挂接)
可以使用mkstemp和tmpfile来安全有效的创建临时文件(这里不推荐用fopen的方法直接创建临时文件,因为可能存在多个实例同时运行的情况,为了避免临时文件相互干扰,建议用前面两个函数来产生随机的临时文件。并且它们还能通过设置文件访问权限,保证文件不会被其他用户读取或者修改)
mkstemp:
生成一个文件描述符,可以用read/write系列的函数访问。
一般情况下,临时文件应该在使用完毕后由进程释放。所以,在调用mkstemp后立刻调用unlink是常用的方法。unlink并不会直接释放文件,它会检查当前的link数是否为1和refrence是否为0。这样,当文件使用完毕后,调用close就会删除该文件,当进程发生异常时,linux系统也会关闭对应的文件系统,这样也保证临时文件同时被删除掉。
输入的文件名必须以XXXXXX结尾,比如:/tmp/temp_file.XXXXXX
tmpfile:
生成一个流描述,可以用C库的I/O函数访问这个资源。同时这个资源不能传递给其它程序。
生成的文件已经unlink了,所以使用fclose就能删除该文件。
(注意:不要使用mktemp, tempnam, tmpnam这些调用,这些函数已经证明有安全性和可靠性问题)
二. 代码自我保护
1. 使用assert
assert(BOOL_EXP)
使用宏定义NDEBUG可以使assert无效(考虑到性能的时候需要这么做),在编译时加入-DNDEBUG标志
使用assert对系统内部关键部位进行保护,但不要对用户接口用assert进行保护
不要犹豫,在你的程序中尽量用assert。
2. 对系统调用失败的处理
不要相信系统调用总会成功。系统调用在接收到外部事件(如sinal)的时候会失败,应用应该自己决定是否重新调用一次。
3.系统调用返回的错误码
首先要查看手册确定每个系统调用的返回值的含义,从而判断成功和失败。
如果失败了,所有的system call都会将错误代码放置到errno中,由于errno在为所有系统调用共用,所以,应该及时将这个值拷贝到本地变量,再进行后续的动作。
错误码直观显示的方法:
strerr可以将数字的错误码转变为易于识别的字符串。
perror可以直接将当前的错误码用易于识别的字符串方式显示到stderr上。
对于错误码,最好能具体处理,比如EINTR表示一个阻塞的过程被sinal或者其它事件打断,这个时候有必要重新执行系统调用。
4. 产生错误以后应该将分配的资源释放后再进入下一步,对于有些资源(malloc的buffer和打开的文件等),进程退出时会自动释放,但有些资源(比如IPC)就不行
三. 编写和使用库
1. 静态库
库的连接查找是与位置有关的,因为库的查找只在顺序的位置上进行,并不会迭代进行(除非使用特定的连接参数),所以,一般把库连接放到命令的最后进行。
静态库只会选择需要的部分连接到应用中。
生成静态库使用ar工具,比如:ar cru libtest.a a.o b.o c.o
2.共享库
动态库会全部连接到应用中(不是部分的有选择的)(当然这种连接仅仅只是包含一个参考)
生成动态库前,所有的object文件编译时必须使用-fPIC选项,这样才会告诉编译器,生成的object文件可以包含在动态库中。换言之,非PIC的代码是不能做为动态库的,否则会在运行时报告段错误。
生成动态库使用gcc -static方式。比如gcc -static a.o b.o c.o -o libtest.so
动态库生成的时候缺省仅添加库名称到应用中,在执行时按照缺省路径查找对应的库(/lib /usr/lib),如果想要在连接时指定完整的路径,需要在连接时使用
-rpath 参数,比如:gcc -o app app.o -L. -ltest -Wl,-rpath,/usr/local/lib
应用连接库的方式(无论静态还是动态库)
1. 使用-l进行连接 比如: -ltest 这个选项表示连接libtest.a/libtest.so
2. 库寻找路径:首先找-L指定的路径,如果是动态库,还会查找LD_LIBRARY_PATH环境变量指定的路径,最后找标准库路径(/lib, /usr/lib)
3. 对于同时存在同名的静态和动态库时,首先连接动态库,如果要强制连接静态库,需要在连接时使用-static选项。
动态库运行时的库查找顺序是:
编译目标代码时指定的动态库搜索路径; (-rpath指定的路径)
环境变量LD_LIBRARY_PATH指定的动态库搜索路径;(库路径用冒号分割)
配置文件/etc/ld.so.conf中指定的动态库搜索路径;
默认的动态库搜索路径/lib;
默认的动态库搜索路径/usr/lib。
3.库的关联
在进行库连接的时候,如果连接库是动态库,且该动态库存在其他关联库,这些关联库会被自动连接到应用中。如果连接的库是静态库,则不会自动连接,必须要在连接的时候找到所有的关联库,并手动将其一并连接到应用。
需要注意的是,静态库连接的时候,如果存在相互关联的库时,可以采用的办法有两个,一个是将所有的库连接写两遍(虽然这么做有些冗余),比如一个应用使用了libc和libm,那么在连接的时候写成 -lc -lm -lc -lm , 这样无论什么关联关系都可以通过连接。还有一个办法是用--start-group 和 --end-group将所有的库连接包围起来,像这样: --start-group -lc -lm --end-group
4.动态加载和卸载库
通过运行时加载库的方法可以用于很多场合,比如plug-in等。
dlopen可以动态加载动态库(如果库已经加载了,则仅仅将参考计数加1),dlsym可以获得动态库的符号引用(有了这个应用,就可以在本地执行动态库中的代码,或者修改变量),dlclose卸载已经加载的动态库(如果同一个库加载了多次,则dlclose仅仅只将参考计数减1)。
(使用上述函数前需要 include ,并且连接libdl.a(so))
如果dlopen或者dlsym返回NULL,表示调用失败,这时,你可以调用dlerr来显示友好的错误信息。
另外需要注意,如果用C++写库函数,最好将所有的函数用extern "c"进行声明,否则外部调用时会出现符号无法找到的问题(C++会自动在符号前面加入一些奇怪的字符,从而实现函数的重载等功能)
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u1/44089/showart_346464.html |
|