为什么这个程序在 Windows 上运行时会出现马车 return?

Why does a carriage return creep in when this program runs on Windows?

我编写了以下程序来将十六进制字符串转换为相应的二进制数据。

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

int main(void) {

  char bf[3];
  char b; /* each byte */

  bf[0] = bf[1] = bf[2] = 0;

  for (;;) {
    for (;;) { 
      bf[0] = getchar();
      if (isspace(bf[0])) continue;
      if (bf[0] == EOF) goto end;
      break;
    }

    for (;;) { 
      bf[1] = getchar();
      if (isspace(bf[1])) continue;
      if (bf[1] == EOF) goto end;
      break;
    }

    b = strtoul(bf, NULL, 16);
    //printf("%s = %d\n", bf, b);
    fwrite(&b, sizeof b, 1, stdout);
  }

 end:
  exit(0);
}

这是一个测试文件:

%cat test.txt
E244050BF817B01D5E271F90052E0DD0
A9A5D1A2468E6908D4CF9951FC544A7B
0A5DF5692545A8856F3EF2CA5440A365
0FE4C9BC9854B042514E4805F0D0C4FF

这是 UNIX 系统上的 运行(输出完全符合预期):

%./hex2bin < /mnt/test.txt | od -t x1
0000000 e2 44 05 0b f8 17 b0 1d 5e 27 1f 90 05 2e 0d d0
0000020 a9 a5 d1 a2 46 8e 69 08 d4 cf 99 51 fc 54 4a 7b
0000040 0a 5d f5 69 25 45 a8 85 6f 3e f2 ca 54 40 a3 65
0000060 0f e4 c9 bc 98 54 b0 42 51 4e 48 05 f0 d0 c4 ff
0000100

这是 Windows 系统上的一个 运行(在字节 7b 后有一个回车符 return):

%./hex2bin.exe < test.txt | od -t x1
0000000 e2 44 05 0b f8 17 b0 1d 5e 27 1f 90 05 2e 0d d0
0000020 a9 a5 d1 a2 46 8e 69 08 d4 cf 99 51 fc 54 4a 7b
0000040 0d 0a 5d f5 69 25 45 a8 85 6f 3e f2 ca 54 40 a3
0000060 65 0f e4 c9 bc 98 54 b0 42 51 4e 48 05 f0 d0 c4
0000100 ff
0000101
%

正确的顺序应该是 [...] 7b 0a [...] 但结果却是 [...] 7b 0d 0a [...]。这里发生了什么?

Windows 文本文件使用字节序列 0D 0A 来标记一行的结束(Unix 只使用一个字节,0A)。 C 标准库在此外部编码和 C 使用的内部 "virtual newline" 字符 ('\n') 之间进行转换。

也就是说,当 Windows 上的 C 程序 运行 将 '\n' 写入文本流时,它会被转换为 0D 0A。逆运算发生在输入上。因为 '\n' 是一个真正的 char 值(通常是 10),其他字节可能会被误解为 '\n'.

如果您不想要这种行为(例如,因为您正在写入或读取二进制数据,而不是文本),您需要使用二进制流,而不是文本流。

对于普通文件,这很简单:只需在调用 fopen 时将 "b" 添加到打开模式。据我所知,对于预定义的流 (stdin / stdout / stderr) 没有可移植的解决方案,但是 Windows 有一个额外的功能来放置现有的流式传输到二进制模式;参见例如this answer.

它显示了以下代码(也见于official Microsoft documentation):

#include <stdio.h>
#include <fcntl.h>
#include <io.h>

...
_setmode( _fileno( stdout ), _O_BINARY );

您的代码中存在一些错误:

  bf[0] = getchar();
  if (isspace(bf[0])) continue;
  if (bf[0] == EOF) goto end;

两个if条件被打破,因为bf[0]是一个charchar 不够大,无法存储 EOF,这是一个特殊的 non-character 值 return,由 getchar 编辑以发出错误信号或 end-of-file .通常,getchar 将 return 成功输入的 non-negative 值和错误时的负值(EOF,通常为 -1)。通过将此值分配给 char,您将截断 EOF 并将其映射到某个真实字符值。

bf[0] == EOF 检查的行为取决于 char 是否是您平台上的签名类型(可能是)。如果是这样,它会将其他一些字符(通常为 255,对应于 ISO-8859-1 中的 ÿ)混淆为 end-of-file。如果 char 是无符号的,这个条件永远不会成立,所以你会得到一个无限循环。

类似地,如果 char 是有符号类型,isspace(bf[0]) 将被破坏,因为如果所有 is... 函数的参数不适合 unsigned char (有一个特殊例外:EOF 是允许的)。

修复方法是先将 getchar 的结果存储在 int 中:

  int c = getchar();
  if (c == EOF) goto end;
  if (isspace(c)) continue;
  bf[0] = c;
  break;