关于unix高级环境编程 编译时的err_sys和err_quit错误的解决办法
其实在附录B中W. Richard Stevens, Stephen A. Rago已经实现了这些函数,把这些实现了的函数写成了头文件,再编译程序时只要包含该头文件就不会出现编译错误的情况了。
apueerror.h
-
#include <errno.h> /* for definition of errno */
-
#include <stdarg.h> /* ISO C variable aruments */
-
static void err_doit(int, int, const char *, va_list);
-
/*
-
* Nonfatal error related to a system call.
-
* Print a message and return.
-
*/
-
void err_ret(const char *fmt, ...)
-
{
-
va_list ap;
-
va_start(ap, fmt);
-
err_doit(1, errno, fmt, ap);
-
va_end(ap);
-
}
-
/*
-
* Fatal error related to a system call.
-
* Print a message and terminate.
-
*/
-
void err_sys(const char *fmt, ...)
-
{
-
va_list ap;
-
va_start(ap, fmt);
-
err_doit(1, errno, fmt, ap);
-
va_end(ap);
-
exit(1);
-
}
-
/*
-
* Fatal error unrelated to a system call.
-
* Error code passed as explict parameter.
-
* Print a message and terminate.
-
*/
-
void err_exit(int error, const char *fmt, ...)
-
{
-
va_list ap;
-
va_start(ap, fmt);
-
err_doit(1, error, fmt, ap);
-
va_end(ap);
-
exit(1);
-
}
-
/*
-
* Fatal error related to a system call.
-
* Print a message, dump core, and terminate.
-
*/
-
void err_dump(const char *fmt, ...)
-
{
-
va_list ap;
-
va_start(ap, fmt);
-
err_doit(1, errno, fmt, ap);
-
va_end(ap);
-
abort(); /* dump core and terminate */
-
exit(1); /* shouldn't get here */
-
}
-
/*
-
* Nonfatal error unrelated to a system call.
-
* Print a message and return.
-
*/
-
void err_msg(const char *fmt, ...)
-
{
-
va_list ap;
-
va_start(ap, fmt);
-
err_doit(0, 0, fmt, ap);
-
va_end(ap);
-
}
-
/*
-
* Fatal error unrelated to a system call.
-
* Print a message and terminate.
-
*/
-
void err_quit(const char *fmt, ...)
-
{
-
va_list ap;
-
va_start(ap, fmt);
-
err_doit(0, 0, fmt, ap);
-
va_end(ap);
-
exit(1);
-
}
-
/*
-
* Print a message and return to caller.
-
* Caller specifies "errnoflag".
-
*/
-
static void err_doit(int errnoflag, int error, const char *fmt, va_list ap)
-
{
-
char buf[MAXLINE];
-
vsnprintf(buf, MAXLINE, fmt, ap);
-
if (errnoflag)
-
snprintf(buf+strlen(buf), MAXLINE-strlen(buf), ": %s",strerror(error));
-
strcat(buf, "\n");
-
fflush(stdout); /* in case stdout and stderr are the same */
-
fputs(buf, stderr);
-
fflush(NULL); /* flushes all stdio output streams */
-
}
-
-
unp.h源码:
-
/* Our own header. Tabs are set for 4 spaces,not 8 */
-
#ifndef __unp_h
-
#define __unp_h
-
#include "/usr/lib/perl5/5.8.0/i386-linux-thread-multi/CORE/config.h"
-
/* configuration options for current OS */
-
/* "../config.h" is generated by configure */
-
/* If anything changes in the following list of #includes,must change
-
acsite.m4 also,for configure's tests */
-
#include<sys/types.h> /* basic system data types */
-
#include<sys/socket.h> /* basic socket definations */
-
#include<sys/time.h> /* timeval{} for select() */
-
#include<time.h> /* timespec{} for pselect() */
-
#include<netinet/in.h> /* sockaddr_in{} and other Internet defns */
-
#include<arpa/inet.h> /* inet(3) functions */
-
#include<errno.h>
-
#include<fcntl.h> /* for nonblocking */
-
#include<netdb.h>
-
#include<signal.h>
-
#include<stdio.h>
-
#include<stdlib.h>
-
#include<string.h>
-
#include<sys/stat.h> /* for S_xxx file mode constants */
-
#include<sys/uio.h> /* for iovec{} and readv /writev */
-
#include<unistd.h>
-
#include<sys/wait.h>
-
#include<sys/un.h> /* for Unix domain sockets */
-
#ifdef HAVE_SYS_SELECT_H
-
#include<sys/select.h> /* for convenience */
-
#endif
-
#ifdef HAVE_SYS_SYSCTL_H
-
#include<sys/sysctl.h>
-
#endif
-
#ifdef HAVE_POLL_H
-
#include<poll.h> /* for convenience */
-
#endif
-
#ifdef HAVE_SYS_EVENT_H
-
#include<sys/event.h> /* for kqueue */
-
#endif
-
#ifdef HAVE_STRINGS_H
-
#include<strings.h> /* for convenience */
-
#endif
-
/* Three headers are normally needed for socket/file ioctl's;
-
* <sys/ioctl.h>,<sys/fillo.h>,and <sys/sockio.h>.
-
*/
-
#ifdef HAVE_SYS_IOCTL_H
-
#include<sys/ioctl.h>
-
#endif
-
#ifdef HAVE_SYS_FILIO_H
-
#include<sys/filio.h>
-
#endif
-
#ifdef HAVE_SYS_SOCKIO_H
-
#include<sys/sockio.h>
-
#endif
-
#ifdef HAVE_PTHREAD_H
-
#include<pthread.h>
-
#endif
-
#ifdef HAVE_NET_IF_DL_H
-
#include<net/if_dl.h>
-
#endif
-
#ifdef HAVE_NETINET_SCTP_H
-
#include<netinet/sctp.h>
-
#endif
-
/* OSF/1 actually disables recv() and send() in <sys/socket.h> */
-
#ifdef __osf__
-
#undef recv
-
#undef send
-
#define recv(a,b,c,d) recvfrom(a,b,c,d,0,0)
-
#define send(a,b,c,d) sendto(a,b,c,d,0,0)
-
#endif
-
#ifndef INADDR_NONE
-
#define INADDR_NONE 0xffffffff /* should have been in <netinet/in.h> */
-
#endif
-
#ifndef SHUT_RD /* these three POSIX names are quite new */
-
#define SHUT_RD 0 /* shutdown for reading */
-
#define SHUT_WR 1 /* shutdown for writing */
-
#define SHUT_RDWR 2 /* shutdown for reading and writing */
-
#endif
-
#ifndef INET_ADDRSTRLEN
-
#define INET_ADDRSTRLEN 16 /* "ddd.ddd.ddd.ddd\0"
-
1234567890123456 */
-
#endif
-
/* Define following even if IPv6 not supported,so we can always allocate
-
an adequately sized buffer,without #ifdefs in the code. */
-
#ifndef INET6_ADDRSRLEN
-
#define INET6_ADDRSTRLEN 46 /* max size of IPv6 address string;
-
"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx" or
-
"xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:ddd.ddd.ddd.ddd\0"
-
1234567890123456789012345678901234567890123456 */
-
#endif
-
/* Define bzero() as a macro if it's not in standard C library. */
-
#ifndef HAVE_BZERO
-
#define bzero(ptr,n) memset(ptr,0,n)
-
#endif
-
/* Older resolves do not have gethostbyname2() */
-
#ifndef HAVE_GETHOSTBYNAME2
-
#define gethostbyname2(host,family) gethostbyname((host))
-
#endif
-
/* The structure returned by recvform_flags() */
-
struct unp_in_pktinfo{
-
struct in_addr ipi_addr; /* dst IPv4 address */
-
int ipi_ifindex; /* received interface index */
-
};
-
/* We need the newer CMSG_LEN() and CMSG_SPACE() macros,but few
-
implementations support them today. These two macros really need
-
an ALIGN() macro, but each implementation does this differently. */
-
#ifndef CMSG_LEN
-
#define CMSG_LEN(size) (sizeof(struct cmsghdr)+(size))
-
#endif
-
#ifndef CMSG_SPACE
-
#define CMSG_SPACE(size) (sizeof(struct cmsghdr)+(size))
-
#endif
-
/* POSIX requires the SUN_LEN() macro, but not all implementations define
-
it(yet). Note that this 4.4BSD macro works regardless whether there is
-
a length field or not. */
-
#ifndef SUN_LEN
-
#define SUN_LEN(su) \
-
(sizeof(*(su)-sizeof((su)->sun_path)+strlen((su)->sun_path))
-
#endif
-
/* POSIX renames "Unix domain" as "local IPC".
-
But not all systems define AF_LCOAL and PF_LOCAL(yet). */
-
#ifndef AF_LOCAL
-
#define AF_LOCAL AF_UNIX
-
#endif
-
#ifndef PF_LOCAL
-
#define PF_LOCAL PF_UNIX
-
#endif
-
/* POSIX requires that an #include of<poll.h> define INFTIM,but many
-
systems still define it in <sys/stropts.h>. We don't want to include
-
all the streams stuff if it's not needed,so we just define INFTIME here.
-
This is the standard value,but there's no guarantee it is -1. */
-
#ifndef INFTIM
-
#define INFTIM (-1) /* infinite poll timeout */
-
#ifdef HAVE_POLL_H
-
#define INFTIM_UNPH /* tell unpxti.h we defined it */
-
#endif
-
#endif
-
/* Following could be derived from SOMAXCONN in <sys/socket.h>,but many
-
kernels still #define it as 5,while actually supporting many more */
-
#define LISTENQ 1024 /* 2nd argument to listen() */
-
/* Miscellaneous constants */
-
#define MAXLINE 4096 /* max text line length */
-
#define BUFFSIZE 8192 /* buffer size for reads and writes */
-
/* Define some port number that can be used for our examples */
-
#define SERV_PORT 9877 /* TCP and UDP */
-
#define SERV_PORT_STR "9877" /* TCP and UDP */
-
#define UNIXSTR_PATH "/tmp/unix.str" /* Unix domain stream */
-
#define UNIXDG_PATH "/tmp/unix.dg" /* Unix domain datagram */
-
/* Following shortens all the type casts of pointer arguments */
-
#define SA struct sockaddr
-
#define HAVE_STRUCT_SOCKADDR_STORAGE
-
#ifndef HAVE_STRUCT_SOCKADDR_STORAGE
-
/*
-
* RFC 3493: protocol-independent placeholder for socket addresses
-
*/
-
#define __ss_MAXSIZE 128
-
#define __ss_ALIGNSIZE (sizeof(int64_t))
-
#ifdef HAVE_SOCKADDR_SA_LEN
-
#define __SS_PADlSIZE (__SS_ALIGNSIZE-sizeof(u_char)-sizeof(sa_family_t))
-
#else
-
#define __SS_PADlSIZE (__SS_ALIGNSIZE-sizeof(sa_family_t))
-
#endif
-
#define __SS_PAD2SIZE (__SS_MAXSIZE-2*__SS_ALIGNSIZE)
-
struct sockaddr_storage{
-
#ifdef HAVE_SOCKADDR_SA_LEN
-
u_char ss_len;
-
#endif
-
sa_family_t ss_family;
-
char __ss_pad1[__SS_PAD1SIZE];
-
int64_t __ss_align;
-
char __ss_pad2[__SS_PAD2SIZE];
-
};
-
#endif
-
#define FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
-
/* default file access permissions for new files */
-
#define DIR_MODE (FILE_MODE | S_IXUSR | S_IXGRP | S_IXOTH)
-
/* default permissions for new directories */
-
typedef void Sigfunc(int); /* for signal handlers */
-
#define min(a,b) ((a)<(b)?(a):(b))
-
#define max(a,b) ((a)>(b)?(a):(b))
-
#ifndef HAVE_ADDRINFO_STRUCT
-
#include "../lib/addrinfo.h"
-
#endif
-
#ifndef HAVE_IF_NAMEINDEX_STRUCT
-
struct if_nameindex{
-
unsigned int if_index; /* 1,2,... */
-
char *if_name; /* null teminated name:""le0,...*/
-
};
-
#endif
-
#ifndef HAVE_TIMESPEC_STRUCT
-
struct timespec{
-
time_t tv_sec; /* seconds */
-
long tv_nsec; /* and nanoseconds */
-
};
-
#endif
va_start等原理以及应用
一、已知函数中的例子。
-
INT
-
PalPrintf(const MMP_CHAR* format, ...) /*…为可变参数,不限定个数和类型.(后面采用专门的指针指向他)*/
-
{
-
-
va_list ap; //初始化指向可变参数列表的指针
-
CHAR buf[384]; //定义存放可变参数转化成格式化字符串的数组。
-
INT result; //定义变量,用于调用函数。
-
-
va_start(ap, format); //将第一个可变参数的地址赋给ap,即ap指向可变参数列表的开始。
-
result = vsprintf(buf, format, ap); //将参数ap和format进行转化形成可显示的格式化字符串。
-
va_end(ap);//将参数ap复位。
-
-
if (result >= 0)
-
{
-
printf(buf); //打印buf数组(字符串数组)
-
-
}
-
-
return result; // 返回数据给函数调用
-
}
二、va_start、va_list、va_end、va_arg的应用。
1. 写一个简单的可变参数的例子
如何写一个简单的可变参数的C函数.写可变参数的 C函数要在程序中用到以下这些宏:
-
void va_start( va_list arg_ptr, prev_param );
-
type va_arg( va_list arg_ptr, type );
-
void va_end( va_list arg_ptr );
va在这里是variable-argument(可变参数)的意思.
这些宏定义在stdarg.h中,所以用到可变参数的程序应该包含这个 头文件。下面我们写一个简单的可变参数的函数,该函数至少有一个整数参数,第二个参数也是整数,是可选的。函数作用只是打印这两个参数的值.
-
void simple_va_fun(int i, ...)
-
{
-
va_list arg_ptr;
-
int j=0;
-
-
va_start(arg_ptr, i);
-
j=va_arg(arg_ptr, int);
-
va_end(arg_ptr);
-
printf("%d %d\n", i, j);
-
return;
-
}
我们可以在我们的头文件中这样声明我们的函数:
extern void simple_va_fun(int i, ...);
我们在程序中可以这样调用:
simple_va_fun(100);
simple_va_fun(100,200);
从这个函数的实现可以看到,我们使用可变参数应该有以下步骤:
-
首先在函数里定义一个va_list型的变量,这里是arg_ptr,这个变量是指向参数的指针.
-
然后用va_start宏初始化变量arg_ptr,这个宏的第二个参数是第一个可变参数的前一个参数,是一个固定的参数.
-
然后用va_arg返回可变的参数,并赋值给整数j. va_arg的第二个参数是你要返回的参数的类型,这里是int型.
-
最后用va_end宏结束可变参数的获取.然后你就可以在函数里使 用第二个参数了.如果函数有多个可变参数的,依次调用va_arg获取各个参数.
如果我们用下面三种方法调用的话,都是合法的,但结果却不一样:
simple_va_fun(100); //结果是:100 -123456789(会变的值)
simple_va_fun(100,200); //结果是:100 200
simple_va_fun(100,200,300);//结果是:100 200
我们看到第一种调用有错误,第二种调用正确,第三种调用尽管结果正确,但和我们函数最初的设计有突。
2.可变参数在编译器中的处理
我们知道va_start,va_arg,va_end是在stdarg.h中被定义成宏的, 由于1)硬件平台的不同 2)编译器的不同,所以定义的宏也有所不同,下面以VC++中stdarg.h里x86平台的宏定义摘录如下(’\’号表示折行):
-
typedef char * va_list;
-
#define _INTSIZEOF(n) ((sizeof(n)+sizeof(int)-1)&~(sizeof(int) - 1))
-
-
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
-
-
#define va_arg(ap,t) (*(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)))
-
-
#define va_end(ap) (ap = (va_list)0)
说明:定义_INTSIZEOF(n)主要是为了某些需要内存的对齐的系统.C语言的函数是从右向左压入堆栈的,图(1)是函数的参数在堆栈中的分布位置.我们看到va_list被定义成char*,有一些平台或操作系统定义为void*.再看va_start的定义,定义为&v+_INTSIZEOF(v),而&v是固定参数在堆栈的地址,所以我们运行va_start(ap, v)以后,ap指向第一个可变参数在堆栈的地址,如图:
高地址|-------------------------------------------|
|函数返回地址 |
|-------------------------------------------|
|....... |
|-------------------------------------------|
|第n个参数(第一个可变参数) |
|-------------------------------------------|<--va_start后ap指向
|第n-1个参数(最后一个固定参数)|
低地址|-------------------------------------------|<-- &v
图( 1 )
然后,我们用va_arg()取得类型t的可变参数值,以上例int型为例,我们看一下va_arg取int型的返回值:
j= ( *(int*)((ap += _INTSIZEOF(int))-_INTSIZEOF(int)) );首先ap+=sizeof(int),已经指向下一个参数的地址了.然后返回ap-sizeof(int)的int*指针,这正是第一个可变参数在堆栈里的地址(图2).然后用*取得这个地址的内容(参数值)赋给j.
高地址|-------------------------------------------------|
|函数返回地址 |
|-------------------------------------------------|
|....... |
|-------------------------------------------------|<--va_arg后ap指向
|第n个参数(第一个可变参数) |
|------------------------------------------------ |<--va_start后ap指向
|第n-1个参数(最后一个固定参数) |
低地址|-------------------------------------------------|<-- &v
图( 2 )
最后要说的是va_end宏的意思,x86平台定义为ap=(char*)0;使ap不再 指向堆栈,而是跟NULL一样.有些直接定义为((void*)0),这样编译器不会为va_end产生代码,例如gcc在linux的x86平台就是这样定义的. 在这里大家要注意一个问题:由于参数的地址用于va_start宏,所以参数不能声明为寄存器变量或作为函数或数组类型. 关于va_start, va_arg, va_end的描述就是这些了,我们要注意的是不同的操作系统和硬件平台的定义有些不同,但原理却是相似的.
3.可变参数在编程中要注意的问题
因为va_start, va_arg, va_end等定义成宏,所以它显得很愚蠢,可变参数的类型和个数完全在该函数中由程序代码控制,它并不能智能地识别不同参数的个数和类型.有人会问:printf中不是实现了智能识别参数吗?那是因为函数printf是从固定参数format字符串来分析出参数的类型,再调用va_arg的来获取可变参数的.也就是说,你想实现智能识别可变参数的话是要通过在自己的程序里作判断来实现的.另外有一个问题,因为编译器对可变参数的函数的原型检查不够严格,对编程查错不利.如果simple_va_fun()改为:
-
void simple_va_fun(int i, ...)
-
{
-
va_list arg_ptr;
-
char *s=NULL;
-
-
va_start(arg_ptr, i);
-
s=va_arg(arg_ptr, char*);
-
va_end(arg_ptr);
-
printf("%d %s\n", i, s);
-
return;
-
}
可变参数为char*型,当我们忘记用两个参数来调用该函数时,就会出现core dump(Unix) 或者页面非法的错误(window平台).但也有可能不出错,但错误却是难以发现,不利于我们写出高质量的程序.
以下提一下va系列宏的兼容性.
System V Unix把va_start定义为只有一个参数的宏:
va_start(va_list arg_ptr);
而ANSI C则定义为:
va_start(va_list arg_ptr, prev_param);
如果我们要用system V的定义,应该用vararg.h头文件中所定义的宏,ANSI C的宏跟system V的宏是不兼容的,我们一般都用ANSI C,所以 用ANSI C的定义就够了,也便于程序的移植.
小结:
可变参数的函数原理其实很简单,而va系列是以宏定义来定义的,实现跟堆栈相关.我们写一个可变函数的C函数时,有利也有弊,所以在不必要的场合,我们无需用到可变参数.如果在C++里,我们应该利用C++的多态性来实现可变参数的功能,尽量避免用C语言的方式来实现.
-------
vsnprintf
vsnprintf,C语言库函数之一,属于可变参数。用于向字符串中打印数据、数据格式用户自定义。
1函数简介
头文件:
#include
函数声明:
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
参数说明:
char *str [out],把生成的格式化的字符串存放在这里.
size_t size [in], str可接受的最大字节数,防止产生数组越界.
const char *format [in], 指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序。
va_list ap [in], va_list变量. va:variable-argument:可变参数
函数功能:将可变参数格式化输出到一个字符数组。
用法类似于vsprintf,不过加了size的限制,防止了内存溢出(size为str所指的存储空间的大小)。
返回值:执行成功,返回写入到字符数组str中的字符个数(不包含终止符),最大不超过size;执行失败,
返回负 值,并置errno.
备注:
linux环境下是:vsnprintf;VC6环境下是:_vsnprintf
2用法实例
-
int mon_log(char* format, ...)
-
{
-
va_list vArgList; //定义一个va_list型的变量,这个变量是指向参数的指针.
-
va_start(vArgList, format); // 用va_start宏初始化变量,这个宏的第二个参数是第一个可变参数的
-
// 前一个参数,是一个固定的参数.
-
vsnprintf(str_tmp, 3, format, vArgList);
-
va_end(vArgList); //用va_end宏结束可变参数的获取
-
return 0;
-
}
//调用上面的函数
mon_log("%d,%d,%d,%d", 1,2,3,4);
返回值
用法:
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <stdarg.h>
-
-
char *make_message(const char *fmt, ...)
-
{
-
/* 初始时假设我们只需要不超过100字节大小的空间 */
-
int n, size = 100;
-
char *p;
-
va_list ap;
-
if ((p = (char *)malloc(size)) == NULL)
-
return NULL;
-
while (1) {
-
/* 尝试在申请的空间中进行打印操作 */
-
va_start(ap, fmt);
-
n = vsnprintf (p, size, fmt, ap);
-
va_end(ap);
-
/* 如果vsnprintf调用成功,返回该字符串 */
-
if (n > -1 && n < size)
-
return p;
-
/* vsnprintf调用失败(n<0)或者p的空间不足够容纳size大小的字符串(n>=size),尝试申请更大的空间*/
-
size *= 2; /* 两倍原来大小的空间 */
-
if ((p = (char *)realloc(p, size)) == NULL)
-
return NULL;
-
}
-
}
-
int main()
-
{
-
/* 调用上面的函数 */
-
char* str = make_message("%d,%d,%d,%d",5,6,7,8);
-
printf("%s\n",str);
-
free(str); // we allocate the memory in the make_message function, so we should release it
-
// by caller(main function).
-
return 0;
-
}
阅读(1713) | 评论(0) | 转发(0) |