关于 printf 的缓冲区

  1. 1. 如何解决
  2. 2. 另一种方法

最近在写程序的时候遇到了一个问题:最开始,为了重定向stdout流,我重写了_write函数,将其重定向至串口:

1
2
3
4
5
6
7
8
9
10
11
/* 该重定向方法适用于 GCC 编译器,如果用的是 ARMCC 编译器请自行查找资料 */
int _write(int fd, char *ptr, int len) {
for (int i = 0; i < len; i++) {
if (*(ptr + i) == '\n') {
HAL_UART_Transmit(&huart1, (uint8_t *)&"\r", 1, 1);
}
HAL_UART_Transmit(&huart1, (uint8_t *)ptr + i, 1, 1);
}

return len;
}

之后调用printf("OK");输出。程序运行后并没有任何输出,调试发现_write函数并没有被执行。

造成这一问题的原因是:在调用printf这一类的 I/O 函数的时候,通常并不会马上生效,因为这些操作会被放入缓冲区(buffer)内,当满足一定条件才会生效(flush),如:

  1. 缓冲区满(full buffering)
  2. 输出换行符时(line buffering)

如何解决

我们可以通过setbuf()setvbuf()函数定义缓冲的行为。先来讲讲setvbuf()

1
int setvbuf(FILE *stream, char *buf, int mode, size_t size);
  • stream 参数指定了需要修改的流
  • buf 参数为指向缓冲区的指针,可以用malloc()或直接定义一个char数组,或者当你不需要缓冲区的时候设为NULL
  • mode 为缓冲的模式,有以下选择:
    • _IOFBF 当缓冲区满时才会刷新(flush)
    • _IOLBF 当检测到换行符\n时就会刷新
    • _IONBUF 无缓冲区
  • size 参数为缓冲区的大小

如果我不需要缓冲区:

1
setvbuf(stdout, NULL, _IONBF, 0);

再讲讲setbuf()函数,其实setbuf()函数是setvbuf()函数的简化版,它隐式指定了setvbuf()的部分参数,以下的操作可以认为是等效的:

1
2
3
4
5
6
7
/* 以下操作可以认为是等效的 */
setbuf(stream, buf);
setvbuf(stream, buf, _IOFBF, BUFSIZ); // fully buffered

/* 以下操作可以认为是等效的 */
setbuf(stream, NULL);
setvbuf(stream, NULL, _IONBF, BUFSIZ); // unbuffered

另一种方法

如果你不嫌烦的话可以在每个printf()函数后执行fflush(stdout),这样不管是否满足刷新的条件输出的操作都会立即生效。

本网站所有文章除特别声明外,均采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。