0

我正在测试GNU libc 手册中的这段代码:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <termios.h>

/* Use this variable to remember original terminal attributes. */

struct termios saved_attributes;

void
reset_input_mode (void)
{
  tcsetattr (STDIN_FILENO, TCSANOW, &saved_attributes);
}

void
set_input_mode (void)
{
  struct termios tattr;
  char *name;

  /* Make sure stdin is a terminal. */
  if (!isatty (STDIN_FILENO))
    {
      fprintf (stderr, "Not a terminal.\n");
      exit (EXIT_FAILURE);
    }

  /* Save the terminal attributes so we can restore them later. */
  tcgetattr (STDIN_FILENO, &saved_attributes);
  atexit (reset_input_mode);

  /* Set the funny terminal modes. */
  tcgetattr (STDIN_FILENO, &tattr);
  tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
  tattr.c_cc[VMIN] = 1;
  tattr.c_cc[VTIME] = 0;
  tcsetattr (STDIN_FILENO, TCSAFLUSH, &tattr);
}

int
main (void)
{
  char c;

  set_input_mode ();

  while (1)
    {
      read (STDIN_FILENO, &c, 1);
      if (c == '\004')          /* C-d */
        break;
      else
        putchar (c);
    }

  return EXIT_SUCCESS;
}

即使终端设置为非规范模式,我仍然需要按 enter 才能接收输入。

但是,如果我将: 更改putchar(c)write(STDOUT_FILENO, &c, sizeof(char)),它会像我想的那样正常工作。

4

1 回答 1

2

你被用户缓冲打败了!putchar(3)是 libc I/O API 的一部分,write(2)而是系统调用——嗯,不完全是系统调用,但为了简单起见,让我们暂时考虑一下。

libc 中有三种类型的缓冲:无缓冲、块缓冲和行缓冲。如果一个流是无缓冲的,数据一写入就进入底层文件(或终端);如果它是块缓冲的,则数据将保存在内存块中,直到填满,然后一次全部写入;但是,如果它是行缓冲的,则在找到换行符时将数据传输到文件(或终端)。

如果流连接到终端,通常是标准输出的情况,它是行缓冲的。所以,这就是你的情况:当你按下回车键时,换行符\n会导致(行)缓冲区被写入标准输出。但是,当您调用 时write(2),会绕过 libc用户缓冲,并将数据写入相应的文件描述符(STDOUT_FILENO)。

所以,正如我之前所说,write(2)是一个系统调用;但实际上,当您调用 时write,您正在调用系统调用的库包装器,它处理系统调用遵循的严格协议(例如,它期望参数的位置等)。

顺便说一句,我在这里所说的一切都可以在 , , 的手册页putchar(3)write(2)找到setbuf(3)。括号中的数字指的是手册中的部分2:用于系统调用,3用于库函数(man man应该为您提供部分及其主题的列表)。

希望能帮助到你。

于 2016-10-02T02:06:32.103 回答