printf函数详解
该文章printf函数原型来自CooCox CoIDE内部组件,为精简版,不能处理浮点数。
- 首先printf函数将格式字符串以及可变参数列表va_list做为参数传递给vprintf函数。
/**
* @brief Outputs a formatted string on the DBGU stream, using a variable number of
* arguments.
*
* @param pFormat Format string.
*/
signed int printf(const char *pFormat, ...)
{
va_list ap;
signed int result;
/* Forward call to vprintf */
va_start(ap, pFormat);
result = vprintf(pFormat, ap);
va_end(ap);
return result;
}
- vprintf函数调用vfprintf函数并将标准输出做为其输出流,将上述格式字符串以及参数列表传递给vfprintf
/**
* @brief Outputs a formatted string on the DBGU stream. Format arguments are given
* in a va_list instance.
*
* @param pFormat Format string.
* @param ap Argument list.
*/
signed int vprintf(const char *pFormat, va_list ap)
{
return vfprintf(stdout, pFormat, ap);
}
- vfprintf主要调用两个函数vsprintf和fputs,前者用于解析格式字符串并将解析后的字符串填充到输出字符数组中,后者用于输出输出字符数组到指定的输出流。
/**
* @brief Outputs a formatted string on the given stream. Format arguments are given
* in a va_list instance.
*
* @param pStream Output stream.
* @param pFormat Format string
* @param ap Argument list.
*/
signed int vfprintf(FILE *pStream, const char *pFormat, va_list ap)
{
char pStr[MAX_STRING_SIZE]; // 将处理后的格式字符串填充到该数组里
char pError[] = "stdio.c: increase MAX_STRING_SIZE\n\r";
/* Write formatted string in buffer */
if (vsprintf(pStr, pFormat, ap) >= MAX_STRING_SIZE) { // 将处理后的格式化字符串写入到输出数组里
fputs(pError, stderr);
while (1); /* Increase MAX_STRING_SIZE */
}
/* Display string */
return fputs(pStr, pStream); // 显示字符串
}
- 以下函数请参考注释阅读
/**
* @brief Stores the result of a formatted string into another string. Format
* arguments are given in a va_list instance.
*
* @param pString Destination string.
* @param length Length of Destination string.
* @param pFormat Format string.
* @param ap Argument list.
*
* @return The number of characters written.
*/
signed int vsprintf(char *pString, const char *pFormat, va_list ap)
{
return vsnprintf(pString /* 解析后的字符串 */,
MAX_STRING_SIZE /* 填充字符数组最大长度 */,
pFormat /* 原始格式字符串 */,
ap /* 参数列表 */);
}
/**
* @brief Stores the result of a formatted string into another string. Format
* arguments are given in a va_list instance.
*
* @param pStr Destination string.
* @param length Length of Destination string.
* @param pFormat Format string.
* @param ap Argument list.
*
* @return The number of characters written.
*/
signed int vsnprintf(char *pStr, size_t length, const char *pFormat, va_list ap)
{
char fill /* 填充字符一般为空格 */;
unsigned char width /* 输出宽度,如%5d输出宽度为5 */;
signed int num = 0 /* 按格式解析后所占位数 */;
signed int size = 0/* 解析后字符串长度 */;
/* Clear the string */
if (pStr) {
*pStr = 0;
}
/* Phase string 开始解析字符串*/
while (*pFormat != 0 && size < length) {
/* Normal character */
if (*pFormat != '%') { // 普通字符直接填充
*pStr++ = *pFormat++;
size++;
}
/* Escaped '%' */
else if (*(pFormat+1) == '%') { // 如果是连着的%除最后一个其他的原样填充
*pStr++ = '%';
pFormat += 2;
size++;
}
/* Token delimiter 开始解析特定格式*/
else {
fill = ' '/* 初始化填充字符为空格 */;
width = 0;
pFormat++;
/* Parse filler 若为0则将填充字符设置为0*/
if (*pFormat == '0') {
fill = '0';
pFormat++;
}
/* Parse width 解析宽度*/
while ((*pFormat >= '0') && (*pFormat <= '9')) {
width = (width*10) + *pFormat-'0';
pFormat++;
}
/* Check if there is enough space 检查数组是否越界*/
if (size + width > length) {
width = length - size;
}
/* Parse type 解析类型*/
switch (*pFormat) {
// 注意va_arg返回值为指定类型的下一个参数的值,注意是下一个
// 解析有符号整形
case 'd':
case 'i': num = PutSignedInt(pStr, fill, width, va_arg(ap, signed int)); break;
// 解析无符号整形
case 'u': num = PutUnsignedInt(pStr, fill, width, va_arg(ap, unsigned int)); break;
// 十六进制小写
case 'x': num = PutHexa(pStr, fill, width, 0, va_arg(ap, unsigned int)); break;
// 十六进制大写
case 'X': num = PutHexa(pStr, fill, width, 1, va_arg(ap, unsigned int)); break;
// 字符串
case 's': num = PutString(pStr, va_arg(ap, char *)); break;
// 字符
case 'c': num = PutChar(pStr, va_arg(ap, unsigned int)); break;
default:
return EOF;
}
pFormat++;
pStr += num;
size += num;
}
}
/* NULL-terminated (final \0 is not counted) */
if (size < length) {
*pStr = 0; // 填充结尾字符
}
else {
*(--pStr) = 0;
size--;
}
return size;
}
- 各个解析函数
/** %c
* @brief Writes a character inside the given string. Returns 1.
*
* @param pStr Storage string.
* @param c Character to write.
*/
signed int PutChar(char *pStr, char c)
{
*pStr = c;
return 1;
}
/** %s
* @brief Writes a string inside the given string.
*
* @param pStr Storage string.
* @param pSource Source string.
* @return The size of the written
*/
signed int PutString(char *pStr, const char *pSource)
{
signed int num = 0;
while (*pSource != 0) {
*pStr++ = *pSource++;
num++;
}
return num;
}
/** %u
* @brief Writes an unsigned int inside the given string, using the provided fill &
* width parameters.
*
* @param pStr Storage string.
* @param fill Fill character.
* @param width Minimum integer width.
* @param value Integer value.
*/
signed int PutUnsignedInt(
char *pStr,
char fill,
signed int width,
unsigned int value)
{
signed int num = 0;
/* Take current digit into account when calculating width */
width--;
/* Recursively write upper digits */
if ((value / 10) > 0) {
num = PutUnsignedInt(pStr, fill, width, value / 10);
pStr += num;
}
/* Write filler characters */
else {
while (width > 0) {
PutChar(pStr, fill);
pStr++;
num++;
width--;
}
}
/* Write lower digit */
num += PutChar(pStr, (value % 10) + '0');
return num;
}
/** %d
* @brief Writes a signed int inside the given string, using the provided fill & width
* parameters.
*
* @param pStr Storage string.
* @param fill Fill character.
* @param width Minimum integer width.
* @param value Signed integer value.
*/
signed int PutSignedInt(
char *pStr,
char fill,
signed int width,
signed int value)
{
signed int num = 0; // 已填充数字位数
unsigned int absolute; // 绝对值
/* Compute absolute value */
if (value < 0) {
absolute = -value;
}
else {
absolute = value;
}
/* Take current digit into account when calculating width */
width--; // 注意每进入该函数一次就减一
/* Recursively write upper digits 以递归的方式写从高位开始填充*/
if ((absolute / 10) > 0) {
if (value < 0) {
num = PutSignedInt(pStr, fill, width, -(absolute / 10));// 递归调用
}
else {
num = PutSignedInt(pStr, fill, width, absolute / 10);
}
pStr += num; // 输出字符串指针偏移
}
else {
/* Reserve space for sign 为符号位保留一个宽度*/
if (value < 0) {
width--;
}
/* Write filler characters */
while (width > 0) { // 如果解析出来的字符宽度小于指定宽度则填充填充字符
PutChar(pStr, fill);
pStr++;
num++;
width--;
}
/* Write sign */
if (value < 0) { // 写符号位
num += PutChar(pStr, '-');
pStr++;
}
}
/* Write lower digit */
num += PutChar(pStr, (absolute % 10) + '0'); // 写入到输出字符串里
return num;
}
/** %x
* @brief Writes an hexadecimal value into a string, using the given fill, width &
* capital parameters.
*
* @param pStr Storage string.
* @param fill Fill character.
* @param width Minimum integer width.
* @param maj Indicates if the letters must be printed in lower- or upper-case.
* @param value Hexadecimal value.
*
* @return The number of char written
*/
signed int PutHexa(
char *pStr,
char fill,
signed int width,
unsigned char maj/* 判断大小写 */,
unsigned int value)
{
signed int num = 0;
/* Decrement width */
width--;
/* Recursively output upper digits */
if ((value >> 4) > 0) { // 先写高位,正好1位16进制可以表示4位二进制
num += PutHexa(pStr, fill, width, maj, value >> 4);
pStr += num;
}
/* Write filler chars */
else {
while (width > 0) {
PutChar(pStr, fill);
pStr++;
num++;
width--;
}
}
/* Write current digit */
if ((value & 0xF) < 10) {
PutChar(pStr, (value & 0xF) + '0');
}
else if (maj) {
PutChar(pStr, (value & 0xF) - 10 + 'A');
}
else {
PutChar(pStr, (value & 0xF) - 10 + 'a');
}
num++;
return num;
}
- 注意以下函数和硬件平台有关,我的硬件平台为STM32故将STM32的串口做为其输出流。
/**
* @brief Implementation of fputs using the DBGU as the standard output. Required
* for printf().
*
* @param pStr String to write.
* @param pStream Output stream.
*
* @return Number of characters written if successful, or -1 if the output
* stream is not stdout or stderr.
*/
signed int fputs(const char *pStr, FILE *pStream)
{
signed int num = 0;
while (*pStr != 0) {
if (fputc(*pStr, pStream) == -1) {
return -1;
}
num++;
pStr++;
}
return num;
}
/**
* @brief Implementation of fputc using the DBGU as the standard output. Required
* for printf().
*
* @param c Character to write.
* @param pStream Output stream.
* @param The character written if successful, or -1 if the output stream is
* not stdout or stderr.
*/
signed int fputc(signed int c, FILE *pStream)
{
if ((pStream == stdout) || (pStream == stderr)) {
PrintChar(c);
return c;
}
else {
return EOF;
}
}
/**
* @brief Transmit a char, if you want to use printf(),
* you need implement this function
*
* @param pStr Storage string.
* @param c Character to write.
*/
void PrintChar(char c)
{
// 将字符输出到串口,UART4为STM32的一个串口
USART_SendData(UART4, (unsigned char)c);
while (USART_GetFlagStatus(UART4, USART_FLAG_TC) == RESET);
}