char *或char **可以伪装成FILE *吗?

在C中,我经常希望以相同的方式处理从文件读取的数据和从字符串数组中读取的数据。 通常从文件读取是用于生产,而字符串用于测试。 我最后写了很多像这样的代码:

void handle_line(char *line, Things *things) { ... } Things *read_from_chars(char *lines[]) { Things *things = Things_new(); for (int i = 0; lines[i] != NULL; i++) { handle_line(lines[i], things); } return things; } Things *read_from_input(FILE *input) { char *line = NULL; size_t linelen = 0; Things *things = Things_new(); while (getline(&line, &linelen, input) > 0) { handle_line(line, things); } return things; } 

这是一种重复的努力。

有没有办法让一个字符串数组伪装成一个FILE *指针? 或相反亦然? 或者有更好的模式来处理这个问题?

对于奖励积分:解决方案应该使char *char **可用于标准文件函数,如fgetsgetline

有一个非标准函数fmemopen ,它允许你打开char []进行读写。 我认为它可以在大多数GNU libc版本和大多数Linux版本中使用。

(这使您可以读取或写入单个字符串,而不是您询问的字符串数组。)

您可以使用包含FILE*和指向数组的指针的区别联合,然后编写一个get_next函数,该函数使用它做正确的事情。

 typedef struct { enum { is_file, is_array } type; union { FILE *file; struct { int index; int size; char **lines; } array; } data; } file_or_array; char *get_next(file_or_array foa) { if (foa.type == is_file) { char *line = NULL; size_t linelen = 0; getline(&line, &linelen, foa.data.file); return line; } else { if (foa.data.array.index < foa.data.array.size) { return strdup(foa.data.array.lines[foa.data.array.index++]); } else { return NULL; } } } 

调用strdup()对于使这项工作保持一致是必要的。 由于getline()返回一个新分配的字符串,调用者需要释放该字符串,因此从数组返回字符串时也会执行相同的操作。 然后呼叫者可以在两种情况下安全地释放它。

处理此问题的最有效方法之一是通过流。 我用它们来隐藏文件/字符串/串口等

我已经推出了自己的流库,主要用于嵌入式系统

一般的想法是: –

 typedef struct stream_s stream_t; struct stream_s { BOOL (*write_n)(stream_t* stream, char* s, WORD n); BOOL (*write_byte)(stream_t* stream, BYTE b); BOOL (*can_write)(stream_t* stream); BOOL (*can_read)(stream_t* stream); BYTE (*read_byte)(stream_t* stream); void* context; }; 

然后你做了一大堆function

 BOOL stream_create(stream_t* stream); BOOL stream_write_n(stream_t* stream, char* s, WORD n); BOOL stream_can_read(stream_t* stream); BYTE stream_read_byte(stream_t* stream); 

等等

使用那些基本函数回调。

流结构中的上下文,用于指向串行,字符串,文件或任何您想要的结构。 然后你有像file_create_stream(stream_t* stream, char* filename)这样的东西,它将使用文件相关函数填充stream上的回调。 然后对于字符串你有类似但处理字符串

设置这种特殊猫的方法不止一种,但一般来说,解决方法是将公共接口的实现隐藏在间接后面,这允许您注入单独的“实现”。

(这个问题的化身也与确保代码版本之间的ABI兼容性有些不同的问题密切相关。)

要在C中解决这个问题,您可以使用类似于C ++中的pimpl with-inheritance(受保护而不是私有d指针,具有重写的受保护构造函数):

您创建了一个不透明的’reader’/’stream’对象(指向C中转发声明的struct w / typedef的指针)和适当命名的构造函数,以实例化注入所需实现的opaque对象。

让我们勾勒出示例头文件,让您了解函数如何组合在一起。 让我们从胆量开始,d指针/ p-impl对象的定义(注意:我省略了一些类似标题保护的样板文件):

reader-private.h

 /* probably should be in its proper C file, but here for clarification */ struct FileReaderPrivateData { FILE * fp; }; /* probably should be in its proper C file, but here for clarification */ struct StringReaderPrivateData { size_t nlines; size_t cursor; char ** lines; }; /* in C we don't have inheritance, but we can 'fix' it using callbacks */ struct ReaderPrivate { int (* close)(void* pData); /* impl callback */ ssize_t (* readLine)(void* pData, char** into); /* impl callback */ /* impl-specific data object, callbacks can type cast safely */ void * data; }; /* works like a plain p-impl/d-pointer, delegates to the callbacks */ struct Reader { struct ReaderPrivate * dPtr; } 

reader.h

 typedef struct Reader* Reader; /* NB: buf would be a pointer to set to a newly allocated line buffer. */ ssize_t readLine(Reader r, char ** buf); int close(Reader r); 

文件reader.h

 #include "reader.h" Reader createFileReader(FILE * fp); Reader createFileReader(const char* path); 

字符串reader.h

 #include "reader.h" Reader createStringReader(const char**, size_t nlines); 

这是在C中使用inheritance执行pimpl / d-pointer的一般模式,因此您可以抽象通过不透明指针访问的公共接口后面的实现内容。 此机制通常用于保证公共接口的各种实现之间的API和ABI兼容性,并实现简单的inheritance模式。

这是使用fcookieopen实现的[IIRC,BSD有类似的东西]:

 // control for string list struct cookie { char **cook_list; // list of strings int cook_maxcount; // maximum number of strings int cook_curidx; // current index into cook_list int cook_curoff; // current offset within item }; int cookie_close(void *vp); ssize_t cookie_read(void *vp,char *buf,size_t size); cookie_io_functions_t cook_funcs = { .read = cookie_open; .close = cookie_close; }; // cookie_open -- open stream FILE * cookie_open(char **strlist,int count,const char *mode) // strlist -- list of strings // count -- number of elements in strlist // mode -- file open mode { cookie *cook; FILE *stream; cook = calloc(1,sizeof(cookie)); cook->cook_list = strlist; cook->cook_maxcount = count; stream = fopencookie(cook,mode,&cook_funcs); return stream; } // cookie_close -- close stream int cookie_close(void *vp) { free(vp); return 0; } // cookie_read -- read stream ssize_t cookie_read(void *vp,char *buf,size_t size) { cookie *cook = vp; char *base; ssize_t totcnt; totcnt = 0; while (size > 0) { // bug out if all strings exhausted if (cook->cook_curidx >= cook->cook_maxcount) break; base = cook->cook_list[cook->cook_curidx]; base += cook->cook_curoff; // if at end of current string, start on the next one if (*base == 0) { cook->cook_curidx += 1; cook->cook_curoff = 0; continue; } // store character and bump buffer and count *buf++ = *base; size -= 1; totcnt += 1; cook->cook_curoff += 1; } return totcnt; } 

如果您只需要调试此function, fopen_strings(char *list[])函数写入:

  • 创建一个临时文件
  • 用模式"r+" fopen
  • 将所有字符串写入其中
  • 删除文件(FILE *仍可对其进行操作,直到它在程序结束时显式或隐式关闭。您可能需要在某些操作系统上跳过此步骤,以防止删除打开的文件。
  • rewind
  • 返回流并让你的程序像普通文件一样使用它。

是否有更好的模式来处理这个问题?

我提出的解决方案是执行函数重载。

提供所有可能的参数:

 Things* readThings(FILE *f, char *l[]) { char *line = NULL; size_t linelen = 0; Things *things = Things_new(); if (f) { while(getline(&line, &linelen, input) > 0) handle_line(line, things); } else { for(int i = 0; lines[i] != NULL; i++) handle_line(lines[i], things); } return things; } Things* readThingsChar(char *l[]){ return readThings(0, l); } Things* readThingsFile(FILE *f){ return readThings(f, 0); } 

如何使用

 FILE *f; char *l[100]; .. Things *a = readThings(f,0); // or readThingsFile(f) Things *b = readThings(0,l); // or readThingsChar(l) 

您可以将其嵌入数据中:

 Things* readThings(char *l[]) { char *line = NULL; size_t linelen = 0; Things *things = Things_new(); FILE *f = NULL; if (l[0][0]==UNIQUE_IDENTIFIER) { f = fopen(l[0]+1); while(getline(&line, &linelen, input) > 0) handle_line(line, things); fclose(f); } else { for(int i = 0; lines[i] != NULL; i++) handle_line(lines[i], things); } return things; } 

如何使用

 char *f[1] = { "_file.txt" }; char *l[100] = { "first line", .. "last line" }; f[0][0] = UNIQUE_IDENTIFIER; Things *a = readThings(f); Things *b = readThings(l);