Ctrl-D (Unix) 和 Ctrl-Z (Windows) 的不同行为
Different behaviour of Ctrl-D (Unix) and Ctrl-Z (Windows)
根据标题,我试图了解 Ctrl+D / Ctrl[=52 的确切行为=]+Z 在带有 gets 的 while 循环中(我需要使用它)。我正在测试的代码如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[80];
while(printf("Insert string: ") && gets(str) != NULL) {
puts(str);
}
return 0;
}
如果我的输入只是 Ctrl+D(或 Ctrl+Z on Windows) gets
returns NULL 并且程序正确退出。不清楚的情况是当我插入类似 house^D^D
(Unix) 或 house^Z^Z\n
(Windows) 的内容时。
- 在第一种情况下,我的解释是
getchar
(或 gets
函数内部类似的东西)等待 read() 获取输入,第一个 Ctrl+D 刷新不为空的缓冲区(因此不是 EOF)然后第二次调用 read() 被触发。
- 在第二种情况下,我注意到第一个 Ctrl+Z 被插入到缓冲区中,而接下来的一切都只是忽略。因此,我的理解是第一个 read() 调用插入
house^Z
并丢弃其他所有内容 returning 5(读取的字符数)。 (我说 5 因为否则我认为一个简单的 Ctrl+Z 应该 return 1 而不会触发 EOF)。然后程序等待来自用户的更多输入,因此第二次调用 read()。
我想知道我对它的工作方式的正确和错误,以及它的哪一部分只是依赖于实现,如果有的话。
此外,我注意到在 Unix 和 Windows 中,即使在触发 EOF 之后,它似乎在接下来的 gets()
调用中重置为 false,我不明白为什么会发生这种情况以及发生在何处代码行。
如有任何帮助,我将不胜感激。
(12/20/2016) 为了避免混淆,我对我的问题进行了大量编辑
CTRL-D 和 CTRL-Z "end of file" 指标分别在 Unix 和 Windows 系统上具有相似的目的,但实现方式却大不相同。
在 Unix 系统上(包括像 Linux 这样的 Unix 克隆)CTRL-D,虽然官方描述为 end-of-file 字符,但实际上是一个分隔符。它与用于分隔行的 end-of-line 字符(通常是 return 或 CTRL-M)的作用几乎相同。这两个字符都告诉操作系统输入行已完成并使程序可用。唯一的区别是 end-of-line 字符会在输入缓冲区的末尾插入一个换行符 (CTRL-J) 以标记行的结尾,而 end-of-file 字符什么也没有已插入。
这意味着当您在 Unix 上输入 house^D^D
时,read
系统调用将首先 return 一个长度为 5 的缓冲区,其中包含 5 个字符 house
。当再次调用 read
以获得更多输入时,它将 return 一个长度为 0 且其中没有字符的缓冲区。由于在普通文件上读取零长度表示已到达文件末尾,因此 gets
库函数也将其解释为文件末尾并停止读取输入。但是,由于它用 5 个字符填充了缓冲区,因此它不会 return NULL 来指示它已到达文件末尾。由于它实际上并没有真正到达文件末尾,因为终端设备实际上不是文件,所以在此之后进一步调用 gets
将进一步调用 read
这将 return any用户键入的后续字符。
Windows CTRL-Z 的处理方式大不相同。最大的区别是它根本没有被操作系统特殊对待。当您在 Windows 上键入 house^Z^Z^M
时,只有回车 return 字符会得到特殊处理。就像在 Unix 上一样,回车 return 使键入的行可供程序使用,尽管在这种情况下,回车 return 和换行符被添加到缓冲区以标记行的结尾。所以结果是 ReadFile
函数 return 是一个 9 字节长的缓冲区,里面有 9 个字符 house^Z^Z^M^J
。
实际上是程序本身,特别是 C 运行时库,对 CTRL-Z 进行了特殊处理。对于 Microsoft C 运行时库,当它在 ReadFile
缓冲区 return 中看到 CTRL-Z 字符时,它会将其视为 end-of-file 标记并忽略之后的所有内容它。使用上一段中的示例,gets
最终调用 ReadFile
以获取更多输入,因为从控制台(或其他设备)读取时不记得它看到 CTRL-Z 字符的事实) 并且它还没有看到 end-of-line (被忽略)。如果您然后再次按回车键,gets
将 return 缓冲区填充 7 个字节 house^Z[=24=]
(添加 0 字节以指示字符串的结尾)。默认情况下,它在从普通文件读取时做同样的事情,如果一个 CTRL-Z 字符出现在一个文件中,它和它后面的所有内容都会被忽略。这是针对 backward-compatibility 和 CP/M 的,它只支持长度为 128 的倍数的文件,并使用 CTRL-Z 标记文本文件真正应该结束的位置。
请注意,上述 Unix 和 Windows 行为都只是对用户输入的正常默认处理。 CTRL-D 的 Unix 处理仅在以规范模式从终端设备读取时发生,并且可以将 "end-of-file" 字符更改为其他字符。在 Windows 上,操作系统从不对 CTRL-Z 进行特殊处理,但 C 运行时库是否进行特殊处理取决于正在读取的 FILE 流是文本模式还是二进制模式。这就是为什么在可移植程序中打开二进制文件时应该始终在模式字符串中包含字符 b
(例如 fopen("foo.gif", "rb")
)。
根据标题,我试图了解 Ctrl+D / Ctrl[=52 的确切行为=]+Z 在带有 gets 的 while 循环中(我需要使用它)。我正在测试的代码如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char str[80];
while(printf("Insert string: ") && gets(str) != NULL) {
puts(str);
}
return 0;
}
如果我的输入只是 Ctrl+D(或 Ctrl+Z on Windows) gets
returns NULL 并且程序正确退出。不清楚的情况是当我插入类似 house^D^D
(Unix) 或 house^Z^Z\n
(Windows) 的内容时。
- 在第一种情况下,我的解释是
getchar
(或gets
函数内部类似的东西)等待 read() 获取输入,第一个 Ctrl+D 刷新不为空的缓冲区(因此不是 EOF)然后第二次调用 read() 被触发。 - 在第二种情况下,我注意到第一个 Ctrl+Z 被插入到缓冲区中,而接下来的一切都只是忽略。因此,我的理解是第一个 read() 调用插入
house^Z
并丢弃其他所有内容 returning 5(读取的字符数)。 (我说 5 因为否则我认为一个简单的 Ctrl+Z 应该 return 1 而不会触发 EOF)。然后程序等待来自用户的更多输入,因此第二次调用 read()。
我想知道我对它的工作方式的正确和错误,以及它的哪一部分只是依赖于实现,如果有的话。
此外,我注意到在 Unix 和 Windows 中,即使在触发 EOF 之后,它似乎在接下来的 gets()
调用中重置为 false,我不明白为什么会发生这种情况以及发生在何处代码行。
如有任何帮助,我将不胜感激。
(12/20/2016) 为了避免混淆,我对我的问题进行了大量编辑
CTRL-D 和 CTRL-Z "end of file" 指标分别在 Unix 和 Windows 系统上具有相似的目的,但实现方式却大不相同。
在 Unix 系统上(包括像 Linux 这样的 Unix 克隆)CTRL-D,虽然官方描述为 end-of-file 字符,但实际上是一个分隔符。它与用于分隔行的 end-of-line 字符(通常是 return 或 CTRL-M)的作用几乎相同。这两个字符都告诉操作系统输入行已完成并使程序可用。唯一的区别是 end-of-line 字符会在输入缓冲区的末尾插入一个换行符 (CTRL-J) 以标记行的结尾,而 end-of-file 字符什么也没有已插入。
这意味着当您在 Unix 上输入 house^D^D
时,read
系统调用将首先 return 一个长度为 5 的缓冲区,其中包含 5 个字符 house
。当再次调用 read
以获得更多输入时,它将 return 一个长度为 0 且其中没有字符的缓冲区。由于在普通文件上读取零长度表示已到达文件末尾,因此 gets
库函数也将其解释为文件末尾并停止读取输入。但是,由于它用 5 个字符填充了缓冲区,因此它不会 return NULL 来指示它已到达文件末尾。由于它实际上并没有真正到达文件末尾,因为终端设备实际上不是文件,所以在此之后进一步调用 gets
将进一步调用 read
这将 return any用户键入的后续字符。
Windows CTRL-Z 的处理方式大不相同。最大的区别是它根本没有被操作系统特殊对待。当您在 Windows 上键入 house^Z^Z^M
时,只有回车 return 字符会得到特殊处理。就像在 Unix 上一样,回车 return 使键入的行可供程序使用,尽管在这种情况下,回车 return 和换行符被添加到缓冲区以标记行的结尾。所以结果是 ReadFile
函数 return 是一个 9 字节长的缓冲区,里面有 9 个字符 house^Z^Z^M^J
。
实际上是程序本身,特别是 C 运行时库,对 CTRL-Z 进行了特殊处理。对于 Microsoft C 运行时库,当它在 ReadFile
缓冲区 return 中看到 CTRL-Z 字符时,它会将其视为 end-of-file 标记并忽略之后的所有内容它。使用上一段中的示例,gets
最终调用 ReadFile
以获取更多输入,因为从控制台(或其他设备)读取时不记得它看到 CTRL-Z 字符的事实) 并且它还没有看到 end-of-line (被忽略)。如果您然后再次按回车键,gets
将 return 缓冲区填充 7 个字节 house^Z[=24=]
(添加 0 字节以指示字符串的结尾)。默认情况下,它在从普通文件读取时做同样的事情,如果一个 CTRL-Z 字符出现在一个文件中,它和它后面的所有内容都会被忽略。这是针对 backward-compatibility 和 CP/M 的,它只支持长度为 128 的倍数的文件,并使用 CTRL-Z 标记文本文件真正应该结束的位置。
请注意,上述 Unix 和 Windows 行为都只是对用户输入的正常默认处理。 CTRL-D 的 Unix 处理仅在以规范模式从终端设备读取时发生,并且可以将 "end-of-file" 字符更改为其他字符。在 Windows 上,操作系统从不对 CTRL-Z 进行特殊处理,但 C 运行时库是否进行特殊处理取决于正在读取的 FILE 流是文本模式还是二进制模式。这就是为什么在可移植程序中打开二进制文件时应该始终在模式字符串中包含字符 b
(例如 fopen("foo.gif", "rb")
)。