Почему происходит возврат каретки, когда эта программа работает в Windows? - PullRequest
3 голосов
/ 25 мая 2019

Я написал следующую программу для преобразования шестнадцатеричной строки в соответствующие двоичные данные.

#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 появляется возврат каретки:

%./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 [...].Что здесь происходит?

1 Ответ

6 голосов
/ 25 мая 2019

В текстовых файлах Windows используется последовательность байтов 0D 0A для обозначения конца строки (Unix использует только один байт, 0A).Стандартная библиотека C переводит между этой внешней кодировкой и внутренним символом «виртуальной новой строки» ('\n'), который использует C.

То есть, когда программа на C, работающая в Windows, записывает '\n' в текстовый поток, он переводится в 0D 0A.Обратная операция происходит на входе.Поскольку '\n' является действительным значением char (обычно 10), другие байты могут быть неверно истолкованы как '\n'.

Если вам не нужно это поведение (например, потому что вы пишете иличтение двоичных данных, а не текста), вам нужно использовать двоичный поток, а не текстовый поток.

Для обычных файлов это легко: просто добавьте "b" в открытый режим при вызове fopen.Насколько мне известно, для предопределенных потоков (stdin / stdout / stderr) не существует переносимого решения, но в Windows есть дополнительная функция для перевода существующего потока в двоичный режим;см., например, этот ответ .

Показывает, что составляет следующий код (также можно увидеть в официальной документации Microsoft ):

#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] - это char.char недостаточно велик для хранения EOF, который представляет собой специальное не символьное значение, возвращаемое getchar для обозначения ошибки или конца файла.Как правило, getchar вернет неотрицательное значение для успешного ввода и отрицательное значение (EOF, обычно -1) при ошибке.Присваивая это значение char, вы усекаете EOF и сопоставляете его с некоторым действительным значением символа.

Поведение проверки bf[0] == EOF зависит от того, является ли char типом со знакомна вашей платформе (это, вероятно, так).Если это так, это может привести к путанице в конце файла (например, 255, что соответствует ÿ в ISO-8859-1).Если char без знака, это условие никогда не выполняется, поэтому вы получите бесконечный цикл.

Аналогично, isspace(bf[0]) прерывается, если char является типом со знаком, потому что все функции is...имеют неопределенное поведение, если их аргумент не помещается внутри unsigned char (с одним специальным исключением: EOF разрешено).

Исправление заключается в том, чтобы сначала сохранить результат getchar в int:

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