面试题答案
一键面试printf
和 scanf
函数家族底层原理
- 缓冲区管理
printf
:printf
通常使用一个缓冲区来暂存要输出的数据。在标准输出流(通常是显示器)的情况下,数据先被写入缓冲区。当缓冲区满、程序调用fflush
函数、程序正常结束或者遇到换行符(\n
)等情况时,缓冲区的数据会被真正输出到设备。这样做的好处是减少系统调用次数,提高效率。例如在向文件输出时,如果每次输出一个字符就进行系统调用写入文件,会因为系统调用的开销导致效率低下。通过缓冲区,数据可以批量写入文件。scanf
:scanf
也涉及缓冲区管理。对于标准输入流(通常是键盘),输入的数据首先被存储在输入缓冲区。scanf
从这个缓冲区读取数据。当缓冲区为空时,系统会等待用户输入。例如,当用户在键盘上输入数据并按下回车键后,这些数据会被送入输入缓冲区,scanf
函数再从缓冲区中按照指定格式提取数据。
- 格式解析算法
printf
:printf
函数的格式字符串包含普通字符和格式说明符。格式说明符以%
开头,后面跟着一个或多个字符来指定数据类型和输出格式。例如,%d
用于输出十进制整数,%f
用于输出浮点数。printf
函数会逐个解析格式字符串,遇到普通字符直接输出,遇到格式说明符则从后续参数列表中取出对应的数据,按照指定格式进行转换和输出。例如,对于printf("%d %f", num, fnum);
,printf
先输出%d
对应的整数num
,再输出%f
对应的浮点数fnum
。格式转换过程涉及到将不同类型的数据转换为可显示的字符串形式,例如将整数转换为十进制字符串,浮点数转换为指定精度的字符串等。scanf
:scanf
的格式字符串同样包含普通字符和格式说明符。普通字符必须与输入缓冲区中的字符精确匹配,格式说明符用于指定如何从输入缓冲区中提取数据并转换为相应的数据类型。例如,scanf("%d %f", &num, &fnum);
,scanf
会从输入缓冲区中按照%d
格式提取一个整数存入num
,再按照%f
格式提取一个浮点数存入fnum
。在提取数据时,scanf
会跳过输入缓冲区中的空白字符(空格、制表符、换行符等),直到遇到与格式说明符匹配的数据。如果遇到不匹配的字符,scanf
会停止读取并返回。
在嵌入式系统中对 printf
优化思路及技术手段
- 减少内存占用
- 简化格式说明符支持:嵌入式系统中可能不需要支持所有标准的
printf
格式说明符。例如,可能只需要支持常见的%d
、%c
等基本格式说明符,对于一些复杂的如%p
(用于输出指针地址)、%a
(用于输出十六进制浮点数)等格式说明符可以不支持。这样可以减少格式解析代码的规模,从而减少内存占用。 - 静态分配缓冲区:避免动态分配缓冲区带来的内存管理开销和额外的内存占用。在嵌入式系统中,预先根据可能输出的最大数据量静态分配一个固定大小的缓冲区。例如,如果预计最大输出长度不超过 100 个字符,可以静态定义一个
char buffer[100];
作为输出缓冲区。这样既避免了动态内存分配的复杂性,又减少了堆内存的碎片化。
- 简化格式说明符支持:嵌入式系统中可能不需要支持所有标准的
- 提高执行效率
- 优化格式解析:采用更高效的格式解析算法。例如,使用查找表(lookup table)来快速确定格式说明符对应的处理函数。对于常见的格式说明符,可以预先构建一个数组,数组元素是对应格式处理函数的指针。这样在解析格式字符串时,通过简单的索引操作就可以找到对应的处理函数,而不需要逐个字符比较格式说明符,从而提高解析速度。
- 减少系统调用:在嵌入式系统中,系统调用往往开销较大。对于输出到外部设备(如串口、LCD 等),可以进一步优化缓冲区管理,使得每次系统调用传输的数据量更大。例如,当向串口输出数据时,不是每输出几个字符就进行一次串口发送操作,而是在缓冲区满或者满足一定条件时才进行一次发送,从而减少串口发送函数的调用次数,提高执行效率。
- 内联函数和宏:对于一些简单的格式转换操作,如将整数转换为字符串,可以使用内联函数或者宏来实现。内联函数和宏在编译时会直接展开代码,避免了函数调用的开销,从而提高执行效率。例如,对于简单的整数转字符串操作,可以定义一个内联函数或者宏来完成,而不是调用标准库中相对复杂且开销较大的函数。