笔记-格式化输出函数

来源:互联网 发布:淘宝是阿里巴巴的吗 编辑:程序博客网 时间:2024/06/07 02:17


参考博客 c printf实现

http://www.cnblogs.com/chenglei/archive/2009/08/06/1540702.html

http://blog.csdn.net/tcpipstack/article/details/8279584

printf的声明
    int _cdecl printf(const char* format, …);
    _cdecl是C和C++程序的缺省调用方式

_CDEDL调用约定:
    1.参数从右到左依次入栈
    2.调用者负责清理堆栈
    3.参数的数量类型不会导致编译阶段的错误


对于x86而言,栈向下生长,函数参数从右向左入栈,因此从第一个固定参数(format)地址向前(向上)移动就可得到其他变参的地址。

va_list相关宏(VC++中stdarg.h里x86平台的宏定义)

typedef char *  va_list;
//_INTSIZEOF(n)宏:将sizeof(n)按sizeof(int)对齐。
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )


//取format参数之后的第一个变参地址,4字节对齐
#define va_start(va_list  ap, format) ( ap = (va_list)&format+ _INTSIZEOF(format) )


//对type类型数据,先取到其四字节对齐地址,再取其值
#define va_arg(va_list  ap,type) 
              ( *(type*)((ap += _INTSIZEOF(type)) -_INTSIZEOF(type)) )

(ap += _INTSIZEOF(type)) -_INTSIZEOF(type))  此表达式结果为 ap 原先所指向的地址,此时ap 指向下一个参数地址。

j=va_arg(arg_ptr, int); 得到一个参数的值,并且arg_ptr指针上移一个_INTSIZEOF(int),即指向下一个可变参数的地址.


#define va_end(va_list  ap)  ( ap = (va_list)0 )

背景知识:

函数调用栈帧结构:
栈底指针在高地址位置,栈顶指针向高地址移动,函数调用时将被调函数实参从右向左依次压栈,然后将函数返回地址(被调函数返回后主调用函数执行的下一条指令地址)压栈,再将函数内变量压栈。
 webmaster@csdn.net

printf的实现原理

比如Z调用A,Z的栈帧:
1.自右向左(约定)压入参数

2.Z中返回地址。即从A返回后Z中下一条指令地址

3.调用者的EBP。由编译器插入指令实现:
"pushl %ebp"
"movl %esp, %ebp"//esp为栈指针
因而形成一个链表。依此可得到调用者的栈顶位置(对于A的EBP,得到Z的EBP地址为0x000f)。

4.局部变量。
对于两个正整数 x, n 总存在整数 q, r 使得
x = nq + r, 其中  0<= r <n                  //最小非负剩余
q, r 是唯一确定的。q = [x/n], r = x - n[x/n]. //c语言实现 x/n结果为整数,x-qn得余数r。
这个是带余除法的一个简单形式。在 c 语言中, q, r 容易计算出来: q = x/n, r = x % n.
所谓把 x 按 n 对齐指的是:若 r=0, 取 qn, 若 r>0, 取 (q+1)n. 这也相当于把 x 表示为:
x = nq + r', 其中 -n < r' <=0                //最大非正剩余
nq 是我们所求。关键是如何用 c 语言计算它。由于我们能处理标准的带余除法,所以可以把这个式子转换成一个标准的带余除法,然后加以处理:
x+n = qn + (n+r'),其中 0<n+r'<=n            //最大正剩余
x+n-1 = qn + (n+r'-1), 其中 0<= n+r'-1 <n    //最小非负剩余
所以 qn = [(x+n-1)/n]n. 用 c 语言计算就是:((x+n-1)/n)*n
/**
 * (n+r'-1) = (x+n-1) - [(x+n-1)/n]n
 * qn = (x+n-1) - (n+r'-1)
 * qn = [(x+n-1)/n]n
 */

若 n 是 2 的方幂, 比如 2^m,则除为右移 m 位,乘为左移 m 位。所以把 x+n-1 的最低 m 个二进制位清 0就可以了。得到:
(x+n-1) & (~(n-1))

/**
 * n = 2^m  //
 *(n - 1) = 2^m -1 // n -1 宏观整数,2^m -1 二进制存储角度;        
 * k...m...321;  等比公式求和
 * ~(n-1)等价于 其对应二进制数低 m位按位取反。
 */

首先看printf函数的定义:

static int printf(const char *fmt, ...){    va_list args;    int i;5  6     va_start(args, fmt);    write(1,printbuf,i=vsprintf(printbuf, fmt, args));    va_end(args);    return i;10 }

 

参数中明显采用了可变参数的定义,而在main.c函数的后面直接调用了printf函数,我们可以看下printf函数的参数是如何使用的。

printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,        NR_BUFFERS*BLOCK_SIZE);printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);

 

先来分析第一个printf调用:

printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS, NR_BUFFERS*BLOCK_SIZE);

可以看到*fmt等于"%d buffers = %d bytes buffer space\n\r”,是一个char 类型的指针,指向字符串的启始位置。而可变的参数在这里是NR_BUFFERS和NR_BUFFERS*BLOCK_SIZE。

其中NR_BUFFERS在buffer.c中定义为缓冲区的页面大小,类型为int;BLOCK_SIZE在fs.h中的定义为

#define BLOCK_SIZE 1024

因此两个可变参数NR_BUFFERS和NR_BUFFERS*BLOCK_SIZE都为int类型;

以前已经分析过可变参数的一系列实现函数va函数。

va_list arg_ptr;

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_list型的变量,这里是arg_ptr,这个变量是指向参数的指针。然后使用va_start使arg_ptr指针指向prev_param的下一位,然后使用va_args取出从arg_ptr开始的type类型长度的数据,并返回这个数据,最后使用va_end结束可变参数的获取。

printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS, NR_BUFFERS*BLOCK_SIZE)中,根据以上的分析fmt指向字符串,args首先指向第一个可变参数,也就是NR_BUFFERS(args在经过一次type va_arg( va_list arg_ptr, type )调用后,会根据type的长度自动增加,从而指向第二个可变参数NR_BUFFERS*BLOCK_SIZE)。

我们先不管write函数的实现,首先来看vsprint。

int vsprintf(char *buf, const char *fmt, va_list args){    int len;    int i;    char * str;    char *s;    int *ip;8  9     int flags;      /* flags to number() */10  11     int field_width;   /* width of output field */12     int precision;      /* min. # of digits for integers; max13                 number of chars for from string */14     int qualifier;      /* 'h', 'l', or 'L' for integer fields */15  16     for (str=buf ; *fmt ; ++fmt) {    //str为最终存放字符串的位置但是他随着字符串增长而增长,buf始终指向最终字符串的启始位置。fmt为格式字符串17         if (*fmt != '%') {                        18             *str++ = *fmt;             //如果不是%则表示这是需要原样打印的字符串,直接复制即可19             continue;20         }21             22         /* process flags */23         flags = 0;24         repeat:25             ++fmt;      /* this also skips first '%' */                            //fmt指向%的后一位26             switch (*fmt) {27                 case '-': flags |= LEFT; goto repeat;28                 case '+': flags |= PLUS; goto repeat;29                 case ' ': flags |= SPACE; goto repeat;                        //判断标志位,并设置flags30                 case '#': flags |= SPECIAL; goto repeat;31                 case '0': flags |= ZEROPAD; goto repeat;32                 }33         34         /* get field width */35         field_width = -1;36         if (is_digit(*fmt))37             field_width = skip_atoi(&fmt);38         else if (*fmt == '*') {39             /* it's the next argument */40             field_width = va_arg(args, int);41             if (field_width < 0) {42                 field_width = -field_width;43                 flags |= LEFT;44             }45         }46  47         /* get the precision */48         precision = -1;49         if (*fmt == '.') {50             ++fmt;   51             if (is_digit(*fmt))52                 precision = skip_atoi(&fmt);53             else if (*fmt == '*') {54                 /* it's the next argument */55                 precision = va_arg(args, int);56             }57             if (precision < 0)58                 precision = 0;59         }60  61         /* get the conversion qualifier */62         qualifier = -1;63         if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {64             qualifier = *fmt;65             ++fmt;66         }67  68         switch (*fmt) {                                 //如果没有上面奇怪的标志位(*/./h/l/L)则fmt仍然指向%的后一位,下面判断这个标志位69         case 'c':70             if (!(flags & LEFT))71                 while (--field_width > 0)72                    *str++ = ' ';73             *str++ = (unsigned char) va_arg(args, int);74             while (--field_width > 0)75                 *str++ = ' ';76             break;77  78         case 's':79             s = va_arg(args, char *);80             len = strlen(s);81             if (precision < 0)82                 precision = len;83             else if (len > precision)84                 len = precision;85  86             if (!(flags & LEFT))87                 while (len < field_width--)88                    *str++ = ' ';89             for (i = 0; i < len; ++i)90                 *str++ = *s++;91             while (len < field_width--)92                 *str++ = ' ';93             break;94  95         case 'o':96             str = number(str, va_arg(args, unsigned long), 8,97                 field_width, precision, flags);98             break;99  100         case 'p':101             if (field_width == -1) {102                 field_width = 8;103                 flags |= ZEROPAD;104             }105             str = number(str,106                 (unsigned long) va_arg(args, void *), 16,107                 field_width, precision, flags);108             break;109  110         case 'x':111             flags |= SMALL;112         case 'X':113             str = number(str, va_arg(args, unsigned long), 16,114                 field_width, precision, flags);115             break;116  117 

       case 'd':                                   //如果是整型,首先设定flags,然后利用number函数将可变参数取出,并根据base(这里是10)然后转换成字符串,赋给str

118         case 'i':119             flags |= SIGN;120         case 'u':121             str = number(str, va_arg(args, unsigned long), 10,122                 field_width, precision, flags);123             break;124  125         case 'n':126             ip = va_arg(args, int *);127             *ip = (str - buf);128             break;129  130         default:131             if (*fmt != '%')//如果格式转换符不是%,则表示出错,直接打印一个%。如果是%,那么格式转换符就是%%,就由下面if(*fmt)只输出一个%132                 *str++ = '%';133             if (*fmt)134                 *str++ = *fmt;//如果格式转换符不正确则输出%+不正确的格式转换符。如果是%%,则只输出一个%135             else136                 --fmt;//如果转换格式符不是上面这些正确的,也不是空,那么直接输出,并返回到判断fmt的for语句;否则就指向末尾了,fmt后退一位,这样在for循环自动再加1进行判断时*fmt的条件就不满足,退出for循环137             break;138         }139     }140     *str = '\0';//设定str字符串的最后一位为'\0'141     return str-buf;//返回值为字符串的长度142  

 

这样我们就实现了根据fmt中的格式转换符将可变参数转换到相应的格式,利用write函数进行输出的目的。

而后者的可变参数memory_end-main_memory_start,根据main.c中的定义

static long buffer_memory_end = 0;
static long main_memory_start = 0;可见为主内存的大小,类型为long。分析同上

而write函数跟fork函数一样是由_syscall*来实现的,内嵌汇编就不多解释了,直接展开就行

write.c

_syscall3(int,write,int,fd,const char *,buf,off_t,count)

unistd.h

#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \
    return (type) __res; \
errno=-__res; \
return -1; \
}



自己的例子:一个写日志函数

SaveLog.h

#ifndef SAVELOG_H
#define SAVELOG_H

#include <stdio.h>

//定义错误,信息 级别

#define INF_SUCCE   0
#define INF_DEBUG   1
#define INF_WRING   2
#define ERR_ERROR  3
#define ERR_ARGUE  4  //参数错误
#define ERR_FOPEN  5  //打开文件错误
#define EER_FWRIT   6  //写文件错误
#define ERR_MALLO  7  //内存申请错误


#define BUFFSIZE 512

#define LOGFILE "../SelfLog.msd"//定义文件名(自定义后缀,带相对路径)。

/**
 * return 0 signify success,other mean wrong
 * be used to write infomation into log file
 */
int writeLog(const char* msg, ...);

#endif

SaveLog.cpp

#include "SaveLog.h"
#include <stdarg.h>
#include <string.h>

int writeLog(const char* msg, ...)
{
 int result = -1;
 int index = 0;
 int count = 0;
 FILE * fd  = NULL;
 va_list arg_ptr  = NULL;
 static char* buff = new char[BUFFSIZE];
 do
 {
  if(msg == NULL)
  {
   result = ERR_ARGUE;
   break;
  }
  if(!buff)
  {
   result = ERR_MALLO;
   break;
  }
  memset(buff,0,BUFFSIZE);
  va_start(arg_ptr,msg);
  vsnprintf(buff, sizeof(char)*BUFFSIZE, msg, arg_ptr);
  fd = fopen(LOGFILE,"a+");
  if(!fd)
  {
   result = ERR_FOPEN;
   break;
  }
  count =fwrite(buff,sizeof(char),strlen(buff),fd);
  if(!count)
  {
   result = EER_FWRIT;
   break;
  }
  result = INF_SUCCE;
 }while(0);
 if(fd)
 {
  fclose(fd);
 }
 if(arg_ptr)
 {
  va_end(arg_ptr);
 }
 if(*buff)
 {
  memset(buff,0,BUFFSIZE);
 }
 return result;
}



0 0
原创粉丝点击