linux0.12-8-11-vsprintf.c

[383 pages]
1. This section does not need to look at how the code is implemented, because the standard C library function;
2. After finishing this book by yourself, it is also possible to come and study if you are interested.

8-11 vsprintf.c program

8-11-1 Function Description

The program mainly includes vsprintf(), which is used to generate formatted output for the parameters. Since this function is in the C library
The flag function of , there is basically no content about the working principle of the design kernel, so it can be skipped. After reading the code directly to the function
instructions for use. Refer to the C library function manual for the usage of the vsprintf() function.

8-11-2 code comment

The code is behind! !

8-11-3 vsprintf() format string

int vsprintf(char *buf, const char *fmt, va_list args)

The vsprintf() function is one of the printf() series of functions. These functions all generate formatted output: Accept the format string fmt that determines the output format, format the parameters with a variable number of format strings, and generate formatted output.

printf sends output directly to the standard handle stdout.
cprintf sends output to the console.
fprintf sends output to a file handle.
A ‘v’ character before printf (eg vfprintf) indicates that the arguments are accepted from the va_list args of the va_arg array.
The ‘s’ character before printf means that the output is sent to the null-terminated string Buf (at this time, the user should ensure that buf has enough space to store the string).

The following describes how to use the format string in detail.
1. Format string
Format strings in the printf family of functions are used to control how the function converts, formats, and outputs its arguments. For each format,
There must be corresponding parameters, too many parameters will be ignored. There are two types of components in the format string,
One is a simple string that is copied directly to the output; the other is a conversion directive string used to format the corresponding argument.

2. Format instruction string
The format directive string has the following form:

%[flags][width][.prec][|h|l|L][type]

Every conversion directive string needs to start with a percent sign (%). in:
[flags] is an optional sequence of flag characters.
[width] is an optional width specifier.
[.prec] is an optional precision indicator.
[|h|l|L] is an optional input length modifier.
[type] is the conversion type character (or called conversion indicator).



Differences between 8-11-4 and the current version

Since this file also belongs to the library function, the functions in the library have been used directly since the kernel version 1.2. That is, the file is deleted.

/*
 * linux/kernel/vsprintf.c
 *
 * (C) 1991 Linus Torvalds
 */

/* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
/*
 * Wirzenius wrote this portably, Torvalds fucked it up :-)
 */
// Lars Wirzenius is a good friend of Linus's, who used to share an office with him at Helsinki University. Developing Linux in Summer 1991
// At that time, Linus was not very familiar with the C language at that time, and he did not know how to use the variable parameter list function. Therefore Lars Wirzenius
// Just wrote this code for the kernel to display information for him. He later (1998) admitted to having a bug in this code until
// It was only discovered in 1994 and corrected. The bug is forgetting to increment the pointer to skip the star when using * as the output field width
// No. up. The bug still exists in this code (line 130). His personal homepage is http://liw.iki.fi/liw/
#include <stdarg.h> // Standard parameter header file. Define a variable argument list as a macro. Mainly explained - a
// type (va_list) and three macros (va_start, va_arg and va_end) for
// vsprintf, vprintf, vfprintf functions.
#include <string.h>// string header file. It mainly defines some embedded functions related to string manipulation.

/* We use the following definitions so we don't use the ctype library */
#define is_digit(c) ((c) >= '0' & amp; & amp; (c) <= '9')// Determine whether character c is a digital character.
// This function converts an alphanumeric string to an integer. The input is a pointer to a pointer to a numeric string, and the return is the resulting value. Otherwise the pointer will move forward.
static int skip_atoi(const char **s)
{<!-- -->
int i=0;

while (is_digit(**s))
i = i*10 + *((*s) + + ) - '0';
return i;
}
// Various symbolic constants for the conversion type are defined here.
#define ZEROPAD 1 /* zero padding */
#define SIGN 2 /* unsigned/signed long integer */
#define PLUS 4 /* display plus */
#define SPACE 8 /* If it is added, then set a space */
#define LEFT 16 /* left adjustment */
#define SPECIAL 32 /* 0x */
#define SMALL 64 /* use lowercase letters */

// Division operation. Input: n is the dividend, base is the divisor; result: n is the quotient, and the function return value is the remainder.
// See Section 4.5.3 for information about embedded assembly.
#define do_div(n,base) ({<!-- --> \
int __res; \
__asm__("divl %4":"=a" (n),"=d" (__res):"0" (n),"1" (0)," r" (base)); \
__res; })

// Convert an integer to a string in the specified base.
// Input: num-integer; base-base; size-string length; precision-number length (precision); type-type option.
// Output: A pointer to the end of the string after the number is converted to a string.
static char * number(char * str, int num, int base, int size, int precision
,int type)
{<!-- -->
char c,sign,tmp[36];
const char *digits="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int i;
    // If the type type indicates lowercase letters, define the lowercase letter set.
    // Mask the zero padding flag in the type if the type indicates left justification (left border).
    // If the radix is less than 2 or greater than 36, exit processing, that is, this program can only process numbers with a radix between 2-32.
if (type & amp;SMALL) digits="0123456789abcdefghijklmnopqrstuvwxyz";
if (type & amp; LEFT) type & amp; = ~ZEROPAD;
if (base<2 || base>36)
return 0;
// If the type indicates that zero is to be filled, then set the character variable c='0', otherwise c is equal to the space character.
    // If the type indicates that it is a signed number and the value num is less than 0, then set the sign variable sign=negative sign, and make num take the absolute value.
    // Otherwise, if the type indicates a plus sign, then set sign=plus sign, otherwise, if the type has a space flag, then sign=space, otherwise set 0.
c = (type & ZEROPAD) ? '0' : ' ' ;
if (type & amp; SIGN & amp; & amp; num<0) {<!-- -->
sign='-';
num = -num;
} else
sign=(type & amp;PLUS) ? ' + ' : ((type & amp;SPACE) ? ' ' : 0);
// If it is signed, the width value is reduced by 1. If the type indicates that it is a special conversion, then reduce the hexadecimal width by 2 bits (for 0x),
    // Subtract 1 for octal width (prepend a zero for octal conversion result).
if (sign) size--;
if (type & SPECIAL)
if (base==16) size -= 2;
else if (base==8) size--;
// If the value num is 0, the temporary string ='0'; otherwise, the value num is converted to character form according to the given base.
i=0;
if (num==0)
tmp[i++]='0';
else while (num!=0)
tmp[i++]=digits[do_div(num,base)];
// If the number of numeric characters is greater than the precision value, the precision value is expanded to a numeric value.
    // Width value size minus the number of characters used to store numeric values.
if (i>precision) precision=i;
size -= precision;
\t
// From here, the required conversion result is actually formed, and temporarily placed in the string str.
    // If there is no zero padding (ZEROPAD) and left alignment (left adjustment) flags in the type, the first in str
    // Fill the number of spaces indicated by the remaining width value. If a signed bit is required, store the sign.
if (!(type & amp;(ZEROPAD + LEFT)))
while(size-->0)
*str + + = ' ';
if (sign)
*str + + = sign;
// If the type indicates that it is a special conversion, put a '0' in the first digit of the octal conversion result; and store '0x' in the hexadecimal.
if (type & SPECIAL)
if (base==8)
*str + + = '0';
else if (base==16) {<!-- -->
*str + + = '0';
*str + + = digits[33];
}
// If there is no left adjustment (left alignment) flag in the type, c characters ('0' or spaces) are stored in the remaining width, see line 51.
if (!(type & LEFT))
while(size-->0)
*str + + = c;
// At this time, i stores the number of numbers with the value num. If the number of digits is less than the precision value, put (precision value -i) '0' in str.
while(i<precision--)
*str + + = '0';
// Fill in the converted numeric characters into str. A total of i.
while(i-->0)
*str + + = tmp[i];
// If the width value is still greater than zero, it means that there is a left-aligned flag in the type flag. put spaces in the remaining width.
while(size-->0)
*str + + = ' ';
return str;// Return the converted pointer to the end of the string.
}
// The following function sends formatted output to a string.
// In order to use formatted output in the kernel, Linus implemented this C standard function in the kernel.
// The parameter fmt is the format string; args is the value of the number change; buf is the output string buffer.
// See the introduction to format conversion characters after this code listing.
int vsprintf(char *buf, const char *fmt, va_list args)
{<!-- -->
int len;
int i;
char * str;// used to store the character string during conversion.
char *s;
int *ip;

int flags; /* flags used by the number() function */

int field_width; /* output field width */
int precision; /* min. the number of integer digits; max. the number of characters in the string */
int qualifier; /* 'h', 'l', or 'L' for integer fields */

// First point the character pointer to buf, then scan the format string, and process each format conversion instruction accordingly.
for (str=buf ; *fmt ; + + fmt) {<!-- -->
// The format conversion instruction strings all start with '%', here scan '%' from the fmt format string to find the beginning of the format conversion string.
    // General characters that are not format instructions are stored in str sequentially.
if (*fmt != '%') {<!-- -->
*str + + = *fmt;
continue;
}
// The following gets the flag field in the format instruction string, and puts the flag constant into the flags variable.
/* process flags */
flags = 0;
repeat:
+ + fmt; /* this also skips first '%' */
switch (*fmt) {<!-- -->
case '-': flags |= LEFT; goto repeat;//Left alignment adjustment.
case ' + ': flags |= PLUS; goto repeat;// put the plus sign.
case ' ': flags |= SPACE; goto repeat;// put spaces.
case '#': flags |= SPECIAL; goto repeat;// is a special conversion.
case '0': flags |= ZEROPAD; goto repeat;// To fill with zeros (that is, '0').
}
    // Take the field value of the current parameter field width and put it into the field_width variable. If there is a value in the width field, it will be directly taken as the width value.
    // If the character '*' is in the width field, it means that the next parameter specifies the width. So calling va_arg takes the width value. If the width value at this time
    // If it is less than 0, the negative number means that it has the flag field '-' flag (aligned left), so this flag needs to be added to the flag variable, and
    // Take the field width value to its absolute value.
/* get field width */
field_width = -1;
if (is_digit(*fmt))
field_width = skip_atoi( &fmt);
else if (*fmt == '*') {<!-- -->
/* it's the next argument */ // There is a bug here, insert + + fmt;
field_width = va_arg(args, int);
if (field_width < 0) {<!-- -->
field_width = -field_width;
flags |= LEFT;
}
}
    // The following code takes the precision field of the format conversion string and puts it into the precision variable. The mark at the beginning of the precision field is '.'.
    // Its processing is similar to that of the width field above. If the precision field is a numeric value, it is directly taken as the precision value. If the precision field is
    // The character '*' means that the next parameter specifies the precision. So calling va_arg takes the precision value. If the width value is less than 0 at this time, the
    // The precision value of the field is taken as 0.
/* get the precision */
precision = -1;
if (*fmt == '.') {<!-- -->
+ + fmt;
if (is_digit(*fmt))
precision = skip_atoi( &fmt);
else if (*fmt == '*') {<!-- -->
/* it's the next argument */ // the same as above should also be inserted here + + fmt;
precision = va_arg(args, int);
}
if (precision < 0)
precision = 0;
}
// The following code parses the length modifier and stores it in the qualifer variable. (For the meaning of h, l, L, please refer to the description after the list).
/* get the conversion qualifier */
qualifier = -1;
if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L') {<!-- -->
qualifier = *fmt;
+ + fmt;
}
// The conversion indicator is analyzed below.
switch (*fmt) {<!-- -->
// If the conversion indicator is 'c', it means that the corresponding argument should be a character. At this time, if the flag field indicates that it is not aligned to the left, the front of the field
// Put 'width field value -1' space characters, and then put parameter characters. If the width field is still greater than 0, it is expressed as left-aligned, then in
// Add 'width value-1' space characters after the parameter character.
case 'c':
if (!(flags & LEFT))
while (--field_width > 0)
*str + + = ' ';
*str + + = (unsigned char) va_arg(args, int);
while (--field_width > 0)
*str + + = ' ';
break;
    // If the conversion indicator is 's', it means that the corresponding parameter is a string. First take the length of the parameter string, if it exceeds the precision threshold value,
    // Then the extended precision domain = string length. At this time, if the flag field indicates that it is not aligned to the left, put 'width value-string length' before the field
    // space characters. Then put in the parameter string. If the width field is still greater than 0, it means left-aligned, then after the parameter string
    // Add 'width value - string length' space characters.
case 's':
s = va_arg(args, char *);
len = strlen(s);
if (precision < 0)
precision = len;
else if (len > precision)
len = precision;

if (!(flags & LEFT))
while (len < field_width--)
*str + + = ' ';
for (i = 0; i < len; + + i)
*str + + = *s + + ;
while (len < field_width--)
*str + + = ' ';
break;
// If the format conversion character is 'o', it means that the corresponding parameter needs to be converted into a string of octal numbers. Call the number() function to process.
case 'o':
str = number(str, va_arg(args, unsigned long), 8,
field_width, precision, flags);
break;
// If the format conversion character is 'p', it means that the corresponding parameter is a pointer type. At this time, if the parameter does not set the width field, the default width
// is 8 and needs to be zeroed. Then call the number() function for processing.
case 'p':
if (field_width == -1) {<!-- -->
field_width = 8;
flags |= ZEROPAD;
}
str = number(str,
(unsigned long) va_arg(args, void *), 16,
field_width, precision, flags);
break;
// If the format conversion instruction is 'x' or 'X', it means that the corresponding parameter needs to be printed as a hexadecimal number for output. 'x' means to use lowercase letters.
case 'x':
flags |= SMALL;
case 'X':
str = number(str, va_arg(args, unsigned long), 16,
field_width, precision, flags);
break;
    // If the format conversion character is 'd','i' or 'u', it means that the corresponding parameter is an integer, and 'd', 'i' represent a symbolic integer, so it needs to be added superior
    // Signed flag. 'u' represents an unsigned integer.
case 'd':
case 'i':
flags |= SIGN;
case 'u':
str = number(str, va_arg(args, unsigned long), 10,
field_width, precision, flags);
break;
    // If the format conversion indicator is 'n', it means that the number of converted output characters so far should be saved to the position specified by the corresponding parameter pointer.
    // First use va_arg() to obtain the parameter pointer, and then store the number of converted characters into the position pointed by the pointer.
case 'n':
ip = va_arg(args, int *);
*ip = (str - buf);
break;
    // If the format conversion character is not '%', it means that the format string is wrong, and directly write a '%' into the output string.
    // If there is still a character at the position of the format conversion character, write the character directly into the output string, and return to line 107 to continue processing
    // format string. Otherwise, it means that the end of the format string has been processed, then exit the loop.
default:
if (*fmt != '%')
*str + + = '%';
if (*fmt)
*str + + = *fmt;
else
--fmt;
break;
}
}
*str = '\0';// Finally add null at the end of the converted string.
return str-buf;// Return the converted string length value.
}