Implement printf and sprintf using Linux kernel variable parameters in C Language
Yesterday, I published an fprintf function implemented using a variable parameter. In fact, it is not completely implemented using a variable parameter, because a pointer like FILE * needs to contain stdio. h. This header file can be used to implement this function. Today, let's take a look at how to discard stdio. h. All 0 starts to implement printf and sprintf. Of course, this code was obtained in the Linux kernel and modified and transplanted by myself, in the DevC ++ compiling environment, it passes the test. Let's look at the Code:
#include
#define NULL 0
// If the string is a number, return the number
static int skip_atoi (const char ** s)
{
int i = 0;
while (isdigit (** s))
i = i * 10 + * ((* s) ++)-'0';
return i;
}
static inline int isdigit (int ch)
{
return (ch> = '0') && (ch <= '9'); // Return the number from 0-9 to the character
}
#define ZEROPAD 1 / * pad with zero * /
#define SIGN 2 / * unsigned / signed long * /
#define PLUS 4 / * show plus * /
#define SPACE 8 / * space if plus * /
#define LEFT 16 / * left justified * /
#define SMALL 32 / * Must be 32 == 0x20 * /
#define SPECIAL 64 / * 0x * /
// This macro is mainly used to implement the determination of what base number to convert
#define __do_div (n, base) ({\
int __res; \
__res = ((unsigned long) n)% (unsigned) base; \
n = ((unsigned long) n) / (unsigned) base; \
__res;})
static char * number (char * str, long num, int base, int size, int precision,
int type)
{
/ * This string array stores 16 digits 0-15, which will be used for base conversion * /
static const char digits [16] = "0123456789ABCDEF";
char tmp [66];
char c, sign, locase;
int i;
/ * locase = 0 or 0x20, produces the same number or letter as the case, maybe the letters are lowercase * /
locase = (type & SMALL);
if (type & LEFT)
type & = ~ ZEROPAD;
if (base <2 || base> 36)
return NULL;
c = (type & ZEROPAD)? '0': '';
sign = 0;
if (type & SIGN) {
if (num <0) {
sign = '-';
num = -num;
size--;
} else if (type & PLUS) {
sign = '+';
size--;
} else if (type & SPACE) {
sign = '';
size--;
}
}
// Detect the base number, whether it is binary or octal or hexadecimal
if (type & SPECIAL) {
if (base == 16)
size-= 2;
else if (base == 8)
size--;
}
i = 0;
if (num == 0)
tmp [i ++] = '0';
else
while (num! = 0)
tmp [i ++] = (digits [__ do_div (num, base)] | locase);
if (i> precision)
precision = i;
size-= precision;
if (! (type & (ZEROPAD + LEFT)))
while (size-> 0)
* str ++ = '';
if (sign)
* str ++ = sign;
if (type & SPECIAL) {
if (base == 8)
* str ++ = '0';
else if (base == 16) {
* str ++ = '0';
* str ++ = ('X' | locase);
}
}
if (! (type & LEFT))
while (size-> 0)
* str ++ = c;
while (i <precision--)
* str ++ = '0';
while (i--> 0)
* str ++ = tmp [i];
while (size-> 0)
* str ++ = '';
return str;
}
int vsprintf (char * buf, const char * fmt, va_list args)
{
int len;
unsigned long num;
int i, base;
char * str;
const char * s;
int flags;
int field_width; / * Bit width output * /
int precision;
int qualifier;
// Judge here, if the% symbol does not exist in the string fmt, the string continues to traverse backward
for (str = buf; * fmt; ++ fmt) {
if (* fmt! = '%') {
* str ++ = * fmt;
continue;
}
// The program sets the flag
flags = 0;
repeat:
++ fmt; / * this also skips first '%' * /
// format control
switch (* fmt) {
case '-':
flags | = LEFT;
goto repeat;
case '+':
flags | = PLUS;
goto repeat;
case '':
flags | = SPACE;
goto repeat;
case '#':
flags | = SPECIAL;
goto repeat;
case '0':
flags | = ZEROPAD;
goto repeat;
}
// Get the width, here is mainly to implement the bit width mechanism of printf
field_width = -1;
if (isdigit (* fmt))
field_width = skip_atoi (& fmt);
else if (* fmt == '*') {
++ fmt;
field_width = va_arg (args, int);
if (field_width <0) {
field_width = -field_width;
flags | = LEFT;
}
}
precision = -1;
if (* fmt == '.') {
++ fmt;
if (isdigit (* fmt))
precision = skip_atoi (& fmt);
else if (* fmt == '*') {
++ fmt;
precision = va_arg (args, int);
}
if (precision <0)
precision = 0;
}
/ * The resulting conversion qualifier * /
qualifier = -1;
if (* fmt == 'h' || * fmt == 'l' || * fmt == 'L') {
qualifier = * fmt;
++ fmt;
}
/ * The default base is decimal * /
base = 10;
// The following is mainly to achieve printf format output. For example:% d,% c,% u ... etc.
switch (* fmt) {
case 'c': // Output as characters
if (! (flags & LEFT))
while (--field_width> 0)
* str ++ = '';
* str ++ = (unsigned char) va_arg (args, int);
while (--field_width> 0)
* str ++ = '';
continue;
case 's': // Output as a string
s = va_arg (args, char *);
len = strnlen (s, precision);
if (! (flags & LEFT))
while (len <field_width--)
* str ++ = '';
for (i = 0; i <len; ++ i)
* str ++ = * s ++;
while (len <field_width--)
* str ++ = '';
continue;
case 'p': // Output as an address
if (field_width == -1) {
field_width = 2 * sizeof (void *);
flags | = ZEROPAD;
}
str = number (str,
(unsigned long) va_arg (args, void *), 16,
field_width, precision, flags);
continue;
case 'n':
if (qualifier == 'l') {
long * ip = va_arg (args, long *);
* ip = (str-buf);
} else {
int * ip = va_arg (args, int *);
* ip = (str-buf);
}
continue;
case '%': // This indicates that the character% is present in the string
* str ++ = '%';
continue;
/ * integer number formats-set up the flags and "break" * /
case 'o': //% o means octal output
base = 8;
break;
case 'x': //% x or% X means hexadecimal output
flags | = SMALL;
case 'X':
base = 16;
break;
case 'd': //% d% i integer output,% u unsigned integer
case 'i':
flags | = SIGN;
case 'u':
break;
default:
* str ++ = '%';
if (* fmt)
* str ++ = * fmt;
else
--fmt;
continue;
}
if (qualifier == 'l') // Output as unsigned long
num = va_arg (args, unsigned long);
else if (qualifier == 'h') {
num = (unsigned short) va_arg (args, int);
if (flags & SIGN)
num = (short) num;
} else if (flags & SIGN)
num = va_arg (args, int);
else
num = va_arg (args, unsigned int);
str = number (str, num, base, field_width, precision, flags);
}
* str = '\ 0'; // String traversal stops when there is \ 0
return str-buf;
}
// variable parameter form to implement sprintf
int mysprintf (char * buf, const char * fmt, ...)
{
va_list args;
int i;
va_start (args, fmt);
// Write the obtained fmt format string to the buf cache
i = vsprintf (buf, fmt, args);
// Release args
va_end (args);
return i;
}
// Variable parameter implementation for myprintf
int myprintf (const char * fmt, ...)
{
char printf_buf [1024];
va_list args;
int printed;
va_start (args, fmt);
printed = vsprintf (printf_buf, fmt, args);
va_end (args);
puts (printf_buf);
return printed;
}
int main (void)
{
myprintf ("Output string: hello world! \ n");
static int sum, a = 3, b = 4;
sum = a + b;
myprintf ("sum (decimal output):% d \ n", sum);
myprintf ("sum (hexadecimal output):% p \ n", sum);
char buffer [128] = {0};
// Save the string in an array buffer
mysprintf (buffer, "Output string: hello world! \ n");
// Output the contents of this buffer in string format
myprintf ("buffer:% s \ n", buffer);
return 0;
}
Running result:
After reading the code, we can see that our program does not contain the stdio. h header file, which implements the printf and sprintf functions. In this case, if you want to implement a printf function by yourself, the code can be called as a file. Haha!