Буферизация при передаче ввода из стандартного ввода в функцию
Я спрашивал об этом вчера, но у меня все еще есть проблемы. Я написал программу на C, которая имеет функцию, которая обрабатывает файлы, которые могут быть переданы ему указателем файла.
void process_my_file(FILE *fptr, ...) {
/* do work */
}
Я спросил, как прочитать входные данные из стандартного ввода и передать их моей функции, и мне порекомендовали попробовать вызвать функцию с аргументом stdin:
my_process_file(stdin, ...);
Это работает, но то, что я действительно хочу сделать, это прочитать из stdin, пока не встретится EOF, а затем передать все входные данные сразу функции. Проблема только с передачей stdin в качестве аргумента состоит в том, что каждый раз, когда пользователь вводит строку ввода и нажимает "enter", программа преждевременно выплевывает соответствующую строку вывода.
Я надеялся на чистое разделение ввода и вывода, чтобы выход выводился только после того, как пользователь сказал EOF (Control-d).
Еще раз спасибо заранее. Я новичок в обучении программированию, и ваши советы очень помогают. Я действительно ценю этот сайт.
- Ларри
3 ответа
Предположим, вам нужно было открыть файл, а затем передать дескриптор файла в вашу функцию. Ваш код в функции все равно должен будет прочитать EOF в этом обычном файле. Кроме того, он должен был бы иметь дело с выделением достаточного пространства для хранения файла и с короткими операциями чтения.
Все это один и тот же набор проблем, с которыми вы должны иметь дело для stdin - единственное возможное отличие состоит в том, что stdin, исходящий из терминала, даст вам короткие чтения для каждой строки ввода, тогда как каждое чтение из канала даст вам короткое чтение считайте размер буфера канала (или атомарные записи, меньшие, чем размер буфера), и обычный файл на диске обычно дает вам только короткое чтение последнего блока файла. Поскольку ваша функция не может заранее определить, сколько места требуется (конечно, не для входов канала или терминала), вы должны быть готовы к динамическому распределению памяти - malloc()
а также realloc()
,
Кроме того, если ваша функция ожидает получить уже прочитанные для нее данные, почему ей передается дескриптор файла (указатель FILE), а не буфер символов и его длина? Вы передаете файловый дескриптор функции, когда вам нужна функция, чтобы использовать ее - для чтения из читаемого дескриптора или для записи в записываемый дескриптор (и, иногда, и то и другое, если дескриптор открыт для чтения и записи).
Вот рабочий пример программы. Мне нужно было что-то придумать, чтобы вылить весь файл в память, обработать его и выкинуть какой-нибудь ответ - поэтому я решил отсортировать файл по символам. Умеренно бессмысленно, но это демонстрирует, что делать. В нем также есть функция сообщения об ошибках аргументов операционной переменной.
Повеселись!
/*
* Demo code for Stackru question 1484693
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <errno.h>
#include <string.h>
static char *arg0;
static void error(const char *fmt, ...)
{
va_list args;
int errnum = errno; /* Catch errno before it changes */
fprintf(stderr, "%s: ", arg0);
va_start(args, fmt);
vfprintf(stderr, fmt, args);
va_end(args);
if (errnum != 0)
fprintf(stderr, " (%d: %s)", errnum, strerror(errnum));
fputc('\n', stderr);
exit(1);
}
static int char_compare(const void *v1, const void *v2)
{
char c1 = *(const char *)v1;
char c2 = *(const char *)v2;
if (c1 < c2)
return -1;
else if (c1 > c2)
return +1;
else
return 0;
}
static void process_my_file(FILE *fp)
{
char *buffer;
size_t buflen = 1024;
size_t in_use = 0;
ssize_t nbytes;
if ((buffer = malloc(buflen)) == 0)
error("out of memory - malloc()");
while ((nbytes = fread(buffer + in_use, sizeof(char), buflen - in_use, fp)) > 0)
{
if (nbytes < 0)
error("error from fread()");
in_use += nbytes;
if (in_use >= buflen)
{
char *newbuf;
buflen += 1024;
if ((newbuf = realloc(buffer, buflen)) == 0)
error("out of memory - realloc()");
buffer = newbuf;
}
}
/* Consistency - number/size vs size/number! */
qsort(buffer, in_use, sizeof(char), char_compare);
fwrite(buffer, sizeof(char), in_use, stdout);
putchar('\n');
free(buffer);
}
int main(int argc, char **argv)
{
arg0 = argv[0];
if (argc > 1)
{
for (int i = 1; i < argc; i++)
{
FILE *fp;
if ((fp = fopen(argv[i], "r")) == 0)
error("failed to open file %s", argv[i]);
process_my_file(fp);
fclose(fp);
}
}
else
process_my_file(stdin);
return(0);
}
Вы можете вызвать это с одним или несколькими именами файлов в качестве аргументов; каждое имя файла сортируется отдельно. Вы можете вставить что-то в него; Вы можете позволить ему читать со стандартного ввода. Я предпочитаю игнорировать возможность того, что fwrite()
а также fclose()
может потерпеть неудачу; Я также предпочитаю игнорировать возможность переполнения на buflen
в process_my_file()
, Вы можете проверить их, если вы выберете. (Обратите внимание, что вывод для каждого файла содержит еще одну новую строку, чем ввод.)
Упражнения для читателя:
- Печатайте непечатаемые символы как escape-последовательности ''\xXX`'.
- Разбейте вывод на строки длиной не более 64 символов.
- Разработать или исследовать альтернативные стратегии распределения, например удвоить пространство для каждого распределения (см. " Практика программирования").
Вы должны будете выполнить предварительную буферизацию самостоятельно, то есть читать stdin до тех пор, пока не будет замечен EOF, а затем передать одну длинную строку (вероятно, состоящую из \n разделенных строк) вашей функции. Или ваша процедура чтения перед буфером могла бы выделить массив символов char*, которые указывают на выделенные строки. Или ваша подпрограмма буфера будет анализировать стандартный ввод и возвращать предварительно обработанную информацию. Зависит от того, что вы хотите сделать с информацией.
Теперь у вас есть " фильтр". Фильтры - это замечательные программы, но, конечно, они применимы не ко всем ситуациям. В любом случае, посмотрите, сможете ли вы сохранить свою программу в качестве фильтра.
Если вы действительно должны прочитать все входные данные перед обработкой, вы должны сохранить их где-нибудь, и нет смысла вызывать функцию обработки с FILE*
(все данные в ФАЙЛЕ * уже прочитаны); Вы можете прочитать все входные данные в массив символов и передать этот массив своей функции.
void process_data(char data[], size_t data_len) { /* do work */ }