我可以用malloc和隐式转换替换open_memstream函数吗?(这是一个提问标题)

7

大家好,

我有一个打印到流的程序。我需要在内存中缓冲此流,然后根据需要将每行打印到实际文件中。

由于fprintf()函数调用必须具有FILE *指针,因此我需要在内存中拥有该指针寻址空间。我曾经使用过open_memstream()函数,但这在Windows上不受支持。

由于malloc()返回一个void *指针,可以根据需要神奇地转换为必要的指针,那么我可以将其用作我的FILE *指针吗?如果可以,有哪些注意事项?我需要注意空间是否会耗尽吗?

更新:

在找到open_memstream()的源代码之后,这比应该更难,看起来他们正在将文件流写入malloc'd空间。

既然是这样,而且我已经得到了他们的源代码,我将尝试使用mingw交叉编译一个可工作的版本以在Windows上运行。


2个回答

8
对于那些在我之后查看的人,请放心!有一个解决方案。如我问题中所述,我正在使用open_memstream(),但这在Windows上不受支持。
由于我有一个File *指针(无法更改为char *),我需要将其重定向到内存中,直到稍后再处理。既然我要处理内存中的文件,我研究了mmap()。它很方便地解决了问题,但只适用于Linux。
不过,Windows包含与mmap()相对应的东西,称为MapViewOfFile()。通过神奇的#ifdef,我已经让它使用必需的内容:
#ifdef WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#endif

接下来,在主方法中,我调用tmpfile()函数,在两个平台上都支持。这将为我打开一个流到保证唯一的临时文件。现在,我有了FILE *指针,需要使用mmap()映射空间。但是mmap()需要一个文件描述符而不是流,所以我使用fileno()函数获取新的文件描述符。

/* create tmp file and get file descriptor */
int fd;
yyout = tmpfile();
fd = fileno(yyout);

现在我有一些更多的#ifdef代码,用于确定需要使用哪个内存映射代码集。请注意两个版本之间映射空间的差异。Windows将映射16384字节,而Linux将映射4096字节。这是因为较小的值在Windows上会导致段错误,正如我的问题中所提到的那样。

#ifdef WIN32
    HANDLE fm;
    HANDLE h = (HANDLE) _get_osfhandle (fd);

    fm = CreateFileMapping(
             h,
             NULL,
             PAGE_READWRITE|SEC_RESERVE,
             0,
             16384,
             NULL);
    if (fm == NULL) { 
            fprintf (stderr, "%s: Couldn't access memory space! %s\n", argv[0],  strerror (GetLastError()));
            exit(GetLastError());
    }
    bp = (char*)MapViewOfFile(
              fm,
              FILE_MAP_ALL_ACCESS,
              0,
              0,
              0);
    if (bp == NULL) { 
            fprintf (stderr, "%s: Couldn't fill memory space! %s\n", argv[0],  strerror (GetLastError()));
            exit(GetLastError());
    }
#else
    bp = mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_FILE|MAP_PRIVATE, fd, 0);
    if (bp == MAP_FAILED) {
            fprintf (stderr, "%s: Couldn't access memory space! %s\n", argv[0], FileName, strerror (errno));
            exit(errno);
    }
#endif

现在有很多工作要完成,数据将被发送到yyout流中。最终会调用flushData()方法。该方法使用空字符完成流的最终处理,然后将其刷新并倒回。然后,将指向内存空间的指针通过函数指针传递,并与正确的流一起打印。

void flushData(void) {
    /* write out data in the stream and reset */ 
    while (currFields < headerFields) { fprintf(yyout, ",\"\""); currFields++; } 
    currFields = 0;
    fprintf(yyout, "%c%c%c", 13, 10, '\0');
    fflush(yyout);
    rewind(yyout);
    if (faqLine == 1) {
        faqLine = 0; /* don't print faq's to the data file */
    }
    else {
        (*printString)(outfile, bp);
        fflush(outfile);
    }
    fflush(yyout);
    rewind(yyout);
}

这是一个用于打印的函数之一。它遍历内存空间并打印每个字符,直到遇到之前打印的null字符。

int printAnsi( FILE *outstream, char *string) {
    /* loop over the chars in string and print them to the outputstream as ansi */
    char * ps = string;
    while (*ps != '\0') {
        fprintf(outstream, "%c", *ps);
        ps++;
    }
    return 0;
}

所有这一切的最终结果是我有了一个类似于 open_memstream() 的流到内存空间的东西,同时也有一个字符指针可以在必要时遍历内存空间。它是跨平台的,并且(看起来)完全可用。
如果有任何人想要更多细节或者有关问题需要我修复的注释,请添加评论。

1
不错!在研究这个问题时,我也发现了这个MIT许可的库,它看起来基本上做了相同的解决方案,尽管它试图跨平台并检测用户环境中可用的内容。https://github.com/Snaipe/fmem/blob/master/src/fmem-winapi-tmpfile.c - kornman00
1
当您完成操作时,除了 fclose 之外,您是否调用 UnmapViewOfFile 或其他任何清理函数? - Ayxan Haqverdili

2
< p >“malloc()”只是给你一块(可能未初始化的)内存,没有任何“神奇”的转换。当你执行 int * buf = malloc(10*sizeof(int)); 时,实际上你是将 “buf” 指向了 10 个未初始化的整型变量。

相应的,使用“FILE”时,可以执行 FILE * f = malloc(10*sizeof(FILE)); 将“f”指向10个未初始化的文件结构体,并且这样做没有任何意义。此外,如果你幸运的话,向未初始化的“FILE”写入数据可能会导致崩溃。

如果你告诉我们你要针对哪些平台以及你想要实现什么目标,那么我们就更容易帮助你。在 POSIX 上,你可以使用 shm_open() 来获取一个指向“共享内存”的文件描述符,并使用 fdopen() 将该文件描述符转换为 FILE*。是的,它可能会用完空间。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接