Можно ли использовать процедуры вывода, выводящие в ФАЙЛ*, для построения строки в C?
-
13-09-2019 - |
Вопрос
У меня плохое предчувствие, что ответ на этот вопрос — «нет», но я хотел высказать это на случай, если у кого-нибудь возникнут умные идеи.
У меня есть набор процедур вывода, которые принимают сложную структуру данных и печатают ее в текстовом формате.У них есть прототипы, такие как:
void print_mystruct(struct mystruct *s, FILE *stream)
Я написал это таким образом, чтобы иметь возможность получать эффективный буферизованный вывод на терминал, в файл, в сеть и т. д.
К сожалению, я не знаю способа, используя стандарт C99, чтобы я мог использовать те же самые процедуры для создания строки в памяти.
Итак, мои вопросы:
- есть ли какой-нибудь умный способ эффективно использовать fputs(), fprintf() и т. д.вывести в строку?
- если нет, существует ли лучшая парадигма, которая может эффективно выполнять как вывод буферизованных файлов, так и построение строк?Лучшее, о чем я могу думать, — это иметь собственную структуру с виртуальной таблицей (вместо ФАЙЛА*).У vtable будет функция вывода, которая либо добавит строку, либо вызовет fwrite.Но тогда мне также придется создать обертки printf.
Есть еще умные идеи?
РЕДАКТИРОВАТЬ:Я обнаружил, что fmemopen()
является частью POSIX.1-2008 (см.: fmemopen()), но не поддерживается широко, по крайней мере, согласно справочной странице GNU libc.
Решение
Портативного способа сделать это не существует.системы glibc (linux) имеют open_memstream/fmemopen , в других системах может не быть чего-то подобного.
Портативный способ — записать в файл и прочитать его обратно в строку., или разделить проблемы.Вместо реализации
void print_mystruct(struct mystruct *s,FILE *f);
Вы бы, например.осуществлять
char *mystruct_2_str(struct mystruct *s);
Который динамически выделяет строку (или передает ее в буфер), форматирует ее в строку с помощью стандартных строковых функций (snprintf и т. д.) и позволяет вызывающей стороне решить, записывать ли ее в ФАЙЛ*
Другие советы
Если вас не интересует поиск в ФАЙЛЕ, есть переносимый (для всех более ранних целей POSIX) способ получить тот же эффект, что и fmemopen
, но это довольно дорого.Сделайте трубу и новую отсоединенную резьбу, и fdopen
записывающий конец канала в вызывающем потоке.Затем новый поток считывает данные из конца канала чтения и помещает данные в строковый буфер.Пусть новый поток вернется, когда он получит EOF из канала;поскольку он отсоединен, очистка не требуется.
Конечно, в структуре FILE нет ничего волшебного, хотя я не знаю ни одной встроенной библиотечной функции, позволяющей создать ее из строкового буфера.Вот хотя бы один вариант определения.
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
Вы можете самостоятельно создать одну из этих структур из строкового буфера и передать ее в свою функцию, а также создать функцию удаления, которая освобождает память.
Вопрос в том, какие вызовы библиотеки CRT вы можете использовать в своей версии?Очевидно, что все, что связано с именем файла, потерпит неудачу, поскольку его нет.Но вы, вероятно, могли бы использовать такие функции, как fwrite, fread и fseek, которые будут манипулировать указателями и при необходимости выделять больше места.
#include <stdio.h>
#include <stdlib.h>
char* file_to_string(FILE *f, int *len) {
if (fseek(f, 0, SEEK_END)) handle_error();
int buflen = ftell(f);
if (len) *len = buflen;
char *buf = malloc(buflen + 1);
buf[buflen] = '\0';
rewind(f);
size_t readlen = fread(buf, 1, buflen, f);
if (readlen != buflen) handle_error();
// in particular, note readlen might not equal buflen in the face of text-mode
// conversions, but tmpfile() indicates binary-mode, so doesn't affect this
// case
return buf;
}
int main() {
FILE *temp = tmpfile();
// use FILE interface
fputs("written to temporary file\n", temp);
char *s = file_to_string(temp, NULL);
// now you have contents as a string
free(s);
// can also rewind the FILE and process it incrementally, etc.
return 0;
}