Chinaunix首页 | 论坛 | 博客
  • 博客访问: 529655
  • 博文数量: 95
  • 博客积分: 1415
  • 博客等级: 上尉
  • 技术积分: 1202
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-20 01:23
文章分类

全部博文(95)

文章存档

2010年(28)

2009年(67)

我的朋友

分类: C/C++

2009-08-25 15:11:04

UnixC深入研究stdio|stdlib|unistd


[引用和转载请标明本文CU blog出处] 作者: Jean.Love
(一)深入printf/scanf
(二)stdio的文件指针锁
(三)fifo的使用事项
(四)Rand的特性
(五)C99的可变参数宏
(六)signal()和sigaction()的详细比较

(一)深入printf/scanf
(1)看看printf如何格式化输出和控制对齐,源代码:
#include
int main(void){
float f=123.456;//浮点数的格式和整数的格式是不一样的,分别介绍
printf("%f\n",f);
printf("%.1f\n",f);//对于浮点数格式m.n n是小数点后面的位数,四舍五入
printf("%.2f\n",f);//同上,m是小数点前面位数
printf("%.3f\n",f);//同上
printf("%.4f\n",f);//小数点位数指定多了,小数部分末尾用0补齐
printf("%1.f\n",f);//m. 不指定n的时候,忽略小数部分,4舍五入
printf("%2.f\n",f);
printf("%3.f\n",f);
printf("%4.f\n",f);//如果m指定得大了,整数部分开头用空格补齐
int i=8;
printf("%*d\n",6,i);//m.n中的m可以用*代替:补齐前面6个空格
printf("%.d\n",i);  //一般
printf("%.5d\n",i); //整数格式m.n中,n是实际显示长度
printf("%2.d\n",i); //m是显示的宽度
printf("%3.2d\n",i);//m是宽度(3),n是实际长度(2),n前面不够的补0
printf("%4.4d\n",i);//同上
printf("%*.*d\n",5,3,i);//m和n可以用参数指定,不必在格式字符串中写死
printf("%+1d\n",i);//显示符号
printf("%+09d\n",i);//m为9,不指定n,m前面(+代表前面)不够的补0
printf("%-07d",i);  //m为7,最多显示7位,不够的后面(-代表后面)补0。如果i超过m位则忽略m的作用
printf("haha\n");
char buf[]="helloworld";
printf("%8.4s\n",buf);//只输出字符串的4位,前面m-n位用空格补齐,m是一共显示m位
printf("%5s%%\n",buf);//不指定n,完全输出,如果m比字符串长度小,忽略m的作用,反之补空格
return 0;
}
输出
> gcc t.cpp && ./a.out
123.456001
123.5
123.46
123.456
123.4560
123
123
123
 123
     8
8
00008
 8
 08
0008
  008
+8
+00000008
8      haha
    hell
helloworld%

 
(2)scanf的弱点(以及用scanf实现的C++的std::cin对象)
man scanf是这么说的
These  functions  return  the  number  of input items successfully matched and assigned, which can be fewer than provided for, or even zero in the event of an early matching failure.
       The value EOF is returned if the end of input is reached before either the first successful conversion or a matching failure occurs.  EOF is  also returned if a read error occurs, in which case the error indicator for the stream (see ferror(3)) is set, and errno is set indicate the error.
 
举例:
1. 如果一次输入的数据超过一个,它会自动分配给下一次的cin或者scanf,不会自动取第一个数字
> vi io.c
"io.c" [New file]
#include
int main(void){
int a,b,c;
printf("input a\n");
scanf("%d",&a);
printf("input b\n");
scanf("%d",&b);
printf("input c\n");
scanf("%d",&c);
printf("%d,%d,%d\n");
return 0;
}

> vi io.cpp
#include
using namespace std;
int main(void){
  int a,b,c;
  cout<<"input a\n";
  cin>>a;
  cout<<"input b\n";
  cin>>b;
  count<  cin>>c;
  cout<  return 0;
}

上面两个程序,运行结果完全一样
> ./a.out
input a
23 4
input b
input c
7
23,4,7

2.如果输入格式非法,那么下一次的cin或者scanf就被取消了。这是为什么呢????????????????????????????????
> ./a.out
input a
e
input b
input c
0,-4196928,5
>                    
这里为什么不在输入了呢? 只是由于第一个cin/scanf类型不匹配,以后的cin/scanf全都得变得无效了?
然后0,-4196928,5又是怎么来的?
解释:
1 读入非法格式数据时scanf返回,输入缓冲区不会被清空,下一次scanf读的是相同内容。
2 0,-4196928,5是随机数,你在scanf之前printf或cout试试,应该是一样的值

3. 危险的scanf
int i=100000;
while(i>10){
   scanf("%d",i);
   //....
}
    如果你输入了一个非数字的值,例如'a',什么结果呢? 程序立即陷入无限循环。
原因是scanf解析错误的时候,输入的内容仍然是在缓存里面,可以用一个小程序来证明:
int main(void)
{
char c;
ungetc('a',stdin);
scanf("%c",&c);
printf("%c\n",c);
}
>./a.out
a   (没有交互)
    如果scanf失败了,下次scanf还是会取缓存里面的内容,而不理会是否需要用户输入了。一种改进的方法是:
#include
int main(void)
{
    int i=1101;
    printf("\r\nPlz input an interger number: \r\n");
    int r=scanf("%d", &i);
    if (i < 1 || i > 100) {
        printf("\r\nERROR: Your choice must be between 1 to 100!\n");
    }
    printf("\r\nINFO: Your choice is %d!\n", i);
    if(r)while(getchar()!='\n');//解决问题!!!!!!!!!!!!!!!
    scanf("%d", &i);
    printf("\r\nINFO: Your choice is %d!\n", i);
    return 0;
}
 
    同理,gets,fgets都是很危险的。同样,C++的cin如果输入到普通的char []类型的数组也是危险的。必须std::cin搭配std::string才能保证安全。如下
#include
#include
using namespace std;
int main(void)
{
string buf;
cin>>buf;
cout<}
------------------------------------------------
(二)stdio的文件指针锁
    C语言可以很方便的对文件加锁。注意不是文件锁,而是FILE*对象的锁。文件锁相关的调用必须依赖操作系统提供的特定API。
>cat s.c
#define  __EXTENSIONS__    /* to expose flockfile and friends in stdio.h */
#include
int main(void){
flockfile(stdin);
funlockfile(stdin);
return 0;
}
记住要把
要把
#define  __EXTENSIONS__    /* to expose flockfile and friends in stdio.h */
放到
#include 的前面

这 样__EXTENSIONS__宏才能起作用,stdio.h肯定是根据__EXTENSIONS__宏来选择隐藏或者暴露lockfile funlockfile函数的。这两个函数的作用是给标准IO流加上了线程锁,以保证一个进程里面只有一个线程在操作某个IO流。
 
当然,gcc不需要加#define __EXTENSIONS__。有些编译器如CC(sun)需要添加这个宏定义。
------------------------------------------------

(三)fifo的使用事项
1. mkfifo函数用于打开一个fifo。这个fifo如果已经存在了,则mkfifo失败,否则将创建一个。
2. open函数打开此命名管道
3. read和write函数是阻塞的,管道的另一端不启动,这一端就等待在那里。
$mkfifo myfifo
$cat w.c
#include
#include
#include
#include
#include
#include
#include
int main(void){
 char fn[]="myfifo";
 char buf[]="hello!";
 int ret,wd,rd,wr;
 wd=open(fn,O_WRONLY|O_NONBLOCK,S_IRWXU);
 if(-1==wd){
  printf("wd open fail,%s\n",strerror(errno));
  return -1;
 }
 wr=write(wd,buf,sizeof(buf));
 if(-1==wr){
  printf("write fail\n");
  return -1;
 }
 printf("%s:%d\n",strerror(errno),wr);
 close(wd);
 getchar();
 return 0;
}
编译gcc w.c -o a.out
可在在控制台$read t
不 过,上面程序稍加修改,去掉O_NONBLOCK的话,如果先一个控制台a.out,另一个控制台$read t
再举一个例程,一个读写fifo的例子程序:
$cat w.c
#include
#include
#include
#include
#include
#include
#include
int main(void){
 char fn[]="myfifo";
 char buf[]="hello!";
 char buf2[5];
 int ret,wd,rd,wr;
 unlink(fn);
 ret=mkfifo(fn,S_IRUSR|S_IWUSR);
 if(-1==ret){
  printf("mkfifo failed\n");
  return -1;
 }
 wd=open(fn,O_WRONLY|O_NONBLOCK,S_IRWXU);
 if(-1==wd){
  printf("wd open fail\n");
  return -1;
 }
 rd=open(fn,O_RDONLY|O_NONBLOCK);
 if(-1==rd){
  printf("rd open fail\n");
  return -1;
 }
 wr=write(wd,buf,sizeof(buf));
 if(-1==wr){
  printf("write fail\n");
  return -1;
 }
 wr=write(wd,buf,sizeof(buf));
 do{
  ret=read(rd,buf2,sizeof(buf2)-1);
  printf("%d,%d,%d,%d,%d\n",ret,buf2[0],buf2[1],buf2[2],buf2[3]);
 }while(ret==sizeof(buf2)-1);
 close(rd);
 close(wd);
 return 0;
}
运行结果
4,104,101,108,108
4,111,33,0,104
4,101,108,108,111
2,33,0,108,111
注意,结果证明了hello!后面有一个字符串结束符号:'0'。
------------------------------------------------

(四)Rand的特性
(1)随机数生成的方式: 必须设定随机数种子
#include
#include
int main(void){
  int rand=random();
  int r=random();
  printf("%d %d\n",rand,r);
  return 0;
}
每次输出都是
[zhang@localhost ~]$ ./a.out 重启程序,重启机器,这个输出都不变!!!!!!!
1804289383 846930886
原因: 伪随机数产生算法都需要一个种子作为起始计算点,如果程序不设置(即使用算法的默认值),那么输出的随机数序列不会发生变化
 
(2)还是随机数的一个问题
#include
#include
#include
#include
int main(void)
{
srand(time(NULL));
int r=abs(rand());
float f=r;
f/=RAND_MAX;
f*=13;
int result=(int)ceil(f);//为了产生1-13之间的随机数,代表扑克牌
printf("%d\n",result);
return 0;
}
在solaris8+CC上面编译,执行了./a.out若干次,输出的结果是:
12,6,7,13,1,7,8,1,2,8,2,3,9,10,3,10,10,4,11,4,5,11,5,12,6,12,13,6,13,1,7,1,1,2,8,9,2,3,10,4,10,,4,4,11
发现:一个数字如果出现了,那么接下来3次运行,再次出现几率也是相当高的。
 
原因:
    种子是根据time(NULL)函数来的, 如果执行间隔太短的话,生产的种子基本上一样,应该会影响产生的随机数。 想取一个随机序列的话,仅初始化一次随机数发生器,然后使用这个发生器依次取出全部数字,这样得到的东西的分布才符合预期。
    随机数发生器可没保证不同种子产生的第一个数的随机分布。如果用时间当随机种子并连续取第一个数值,相当于把一系列连续数字(如 123333 123334 123335)代入随机数公式。
------------------------------------------------

(五)C99的可变参数宏
C89中规定了可变参的声明方式。实际上,va_list,va_start,va_arg,va_end就是在传入参数的堆栈上面进行指针操作。可以自定义指针操作来模拟stdarg的功能
> cat v.C
#include
#include
void f(int param1,...){
  va_list ag;//usually it's a char*
  va_start(ag,param1);//ag=¶m1+sizeof(param1);
  while(1){
    int i1=va_arg(ag,int);
    if(i1==0)break;
    printf("%d\n",i1);
  }
  printf("%p\n",¶m1);
  char** pargc=(char**)¶m1;//模拟va_list的声明
  printf("%s\n",*(pargc+5));   //模拟va_arg取参数
  int* pargs=(int*)¶m1;
  ++pargs;//模拟va_start的过程
  printf("%d, %d, %d, %d, %d\n",*pargs,*(pargs+1),*(pargs+2),*(pargs+3),*(pargs+4));
}
int main(void){
  f(1,2,0,4,5,"20");
  return 0;
}
******************************************************************
C99中规定了新的宏参数__VA_ARGS__,它可用来表示f(...)里面的省略号里面的内容
注意: C89无法兼容它。
使用Oracle的Pro*c遇到c99类型的__VA_ARGS__宏就出错:
预编译的时候加上 parse=none 选项
例如:
    proc iname=***  parse=none
如果是多线程,还需要加上thread=true选项

-----------------------------------------------
(六)signal()和sigaction()的详细比较
    Unix库函数fcntl可能被信号打断,不管程序是否捕捉和处理了信号。测试环境solaris10+CC。想要解决这个问题,必须用sigaction()替代signal()注册相应的信号。
    信号的作用就是用来中断程序的正常流程,以作特殊处理。所以除了忽略的或默认行为是忽略的信号,都可以中断阻塞的fcntl,比如你这里的SIGHUP,SIGINT,SIGALRM等等,至于“为什么“,信号本来就是干这事儿的。
    其次,POSIX.1允许重启被中断的系统调用,但是这不是必需的,如FreeBSD, Linux默认重启被中断的系统调用,而Solaris就是出错返回,设置errno为EINTR。而且sigaction可以控制系统调用是否需要重 启,所以这些实现都是合理的。一种特殊的情况是: sleep()函数总是会被SIGALRM中断,不管是否用sigaction做过相应的设置
有问题的程序:
#include
#include
#include
#include
#include
#include
#include
extern int errno;
extern "C" void s(int sig){
printf("signal handle: %d\n",sig);
}
int main(void){
int fdes=open("kg",O_RDWR|O_CREAT,0664);
if(fdes<0){
  printf("open fail\n");
  exit(1);
}
signal(SIGALRM,&s);
struct flock lock;
lock.l_type=F_WRLCK;
lock.l_start=0;
lock.l_whence=SEEK_SET;
lock.l_len=0;
alarm(5);
if(fcntl(fdes,F_SETLKW,&lock)==-1){//不能拿到锁的时候一定阻
  signal(SIGALRM,SIG_IGN);
  alarm(0);
  printf("error=%d,%s\n",errno,strerror(errno));
  close(fdes);
  exit(1);
}
printf("lock got\n");
signal(SIGALRM,SIG_IGN);
sleep(20);
close(fdes);
return 0;
}
(1)编译运行:
一个term里面./a.out (称为进程a)
另一个term里面也瞬间./a.out (称为进程b)

(2)运行结果:
a进程运行以后对文件加锁,打印"lock got",然后睡20s,结束
b进程启动以后fcntl会等待(因为F_SETLKW),然后第5s的时候,进程接受到了SIGALRM,(因为alarm(5)的原因,然后打印
signal handle: 14
error=4,Interrupted system call

(3)问题:
a)进程为什么没有执行信号处理函数s从而打印出"signal handle: 14"? 因为在sleep之前都忽略了SIGALRM,所以a不会执行s。
b)进程的信号处理函数已经起了作用,为什么fcntl(fdes,F_SETLKW,&lock)仍然被信号中断?信号处理函数生效,系统调用也能被信号中断,至于系统调用是否会重启,取决于实现,也就是说看文档,没有标准。

(4)把原程序中的alarm()和signal(SIGALRM)去掉了,换成了signal(SIGHUP),然后我在a,b两个进程运行的时候:
>kill -HUP

同样,b进程打印
signal handle: 14
error=4,Interrupted system call

并退出。也就是说在solaris10这个环境上面,fcntl会被所有的signal中断。

------------------------------------------------------------------------
能够工作的版本:
#include
#include
#include
#include
#include
#include
#include
extern int errno;
extern "C" void s(int sig,siginfo_t *, void *){
printf("signal handle: %d\n",sig);
}
int main(void){
int fdes=open("kg",O_RDWR|O_CREAT,0664);
if(fdes<0){
  printf("open fail\n");
  exit(1);
}
struct sigaction act,oact;
sigemptyset(&act.sa_mask);
act.sa_sigaction=s;
act.sa_flags=SA_RESTART;
sigaction(SIGALRM,&act,&oact);
struct flock lock;
lock.l_type=F_WRLCK;
lock.l_start=0;
lock.l_whence=SEEK_SET;
lock.l_len=0;
alarm(5);
if(fcntl(fdes,F_SETLKW,&lock)==-1){
  alarm(0);
  printf("error=%d,%s\n",errno,strerror(errno));
  close(fdes);
  exit(1);
}
printf("lock got\n");
signal(SIGALRM,SIG_IGN);
sleep(60);
close(fdes);
return 0;
}

现在两个进程能同时运行,不会出现system call interrupted了
再举一个例子,做一个定时器,每隔2秒输出一个字符串
"f.cpp" 21 lines, 394 characters
#include
#include
#include
extern "C" void f(int s,siginfo_t *, void* ){
  printf("sig handler:%d\n",s);
  alarm(2);
}
int main(void){
struct sigaction act,oact;
sigemptyset(&act.sa_mask);
act.sa_sigaction=f;
act.sa_flags=SA_RESTART;
sigaction(SIGALRM,&act,&oact);
alarm(2);
while(1)pause();//把pause改为sleep(x)效果相同
  return 0;
}
 
如果不使用sigaction而是使用signal的话,pause/sleep被中断以后,系统会打印一条奇异的信息:
Alarm clock
然后退出。
    所以既然signal是个语义不清晰的函数,应当尽量使用sigaction。sleep的问题比较复杂,引用CU网友timespace的一段叙述,ISO标准并没有规定sleep的实现方式,所以在不同的环境下可能导致不同的结果:

------------------------------------------------------------------------
NAME
    sleep - suspend execution for an interval of time

SYNOPSIS
    #include
    unsigned sleep(unsigned seconds);
DESCRIPTION
    The sleep() function shall cause the calling thread to be suspended from execution until either the number of realtime seconds specified by the argument seconds has elapsed or a signal is delivered to the calling thread and its action is to invoke a signal-catching function or to terminate the process. The suspension time may be longer than requested due to the scheduling of other activity by the system.
    If a SIGALRM signal is generated for the calling process during execution of sleep() and if the SIGALRM signal is being ignored or blocked from delivery, it is unspecified whether sleep() returns when the SIGALRM signal is scheduled. If the signal is being blocked, it is also unspecified whether it remains pending after sleep() returns or it is discarded.
    If a SIGALRM signal is generated for the calling process during execution of sleep(), except as a result of a prior call to alarm(), and if the SIGALRM signal is not being ignored or blocked from delivery, it is unspecified whether that signal has any effect other than causing sleep() to return.
    If a signal-catching function interrupts sleep() and examines or changes either the time a SIGALRM is scheduled to be generated, the action associated with the SIGALRM signal, or whether the SIGALRM signal is blocked from delivery, the results are unspecified.
    If a signal-catching function interrupts sleep() and calls siglongjmp() or longjmp() to restore an environment saved prior to the sleep() call, the action associated with the SIGALRM signal and the time at which a SIGALRM signal is scheduled to be generated are unspecified. It is also unspecified whether the SIGALRM signal is blocked, unless the process' signal mask is restored as part of the environment.
RATIONALE
    There are two general approaches to the implementation of the sleep() function. One is to use the alarm() function to schedule a SIGALRM signal and then suspend the calling thread waiting for that signal. The other is to implement an independent facility. This volume of IEEE Std 1003.1-2001 permits either approach.
    In order to comply with the requirement that no primitive shall change a process attribute unless explicitly described by this volume of IEEE Std 1003.1-2001, an implementation using SIGALRM must carefully take into account any SIGALRM signal scheduled by previous alarm() calls, the action previously established for SIGALRM, and whether SIGALRM was blocked. If a SIGALRM has been scheduled before the sleep() would ordinarily complete, the sleep() must be shortened to that time and a SIGALRM generated (possibly simulated by direct invocation of the signal-catching function) before sleep() returns. If a SIGALRM has been scheduled after the sleep() would ordinarily complete, it must be rescheduled for the same time before sleep() returns. The action and blocking for SIGALRM must be saved and restored.
    Historical implementations often implement the SIGALRM-based version using alarm() and pause(). One such implementation is prone to infinite hangups, as described in pause(). Another such implementation uses the C-language setjmp() and longjmp() functions to avoid that window. That implementation introduces a different problem: when the SIGALRM signal interrupts a signal-catching function installed by the user to catch a different signal, the longjmp() aborts that signal-catching function. An implementation based on sigprocmask(), alarm(), and sigsuspend() can avoid these problems.
阅读(1929) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~