使用 strtok 解析 mmaped 文件?

Parsing mmaped file with strtok?

这是我的问题:我想映射文件 "filename.txt",它基本上每行包含两对字符串:

"string1 string2
 string3 string4
 string5 string6..."

然后我想使用 strtok 分隔不同的字符串。

所以我这样映射文件:

// open file
if ((fdsrc = open("filename.txt", O_RDONLY)) < 0) {
        fprintf(stderr, "src open error");
        exit(1);
    }

// get the size of the file
if (fstat(fdsrc, &statbuf) < 0) {
    fprintf(stderr, "fstat error");
    exit(1);
}

// mmap the file
if ((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdsrc, 0)) == (caddr_t) -1) {
    fprintf(stderr, "mmap src");
    exit(1);
}

当我运行行

printf("src: %s \n", src);

它正确地打印了文件的内容!

但是当我尝试将单词分开时

char* token;
token = strtok(src, " \n");
while (token != NULL) {
    token = strtok(NULL, " \n");
}

输出是分段错误。 那为什么我不能使用 StrTok?

strtok() 修改您向其传递指针的 char 数组。

mmap 文件处于只读模式,因此当 strtok 试图修改内存时您会遇到违规。

以读写模式mmap文件是个坏主意,文件会被修改并可能损坏。

strtok 不适合您的目的,编写您自己的匹配函数,不修改其参数数组和 returns 偏移量和长度。

另请注意,mmapped 内存不应超出文件大小进行访问,并且不一定 '[=17=]' 终止,因此您不应使用字符串函数搜索它( strchrstrstrstrlen...) 也不会从中复制 (strcpy).

您的文件映射为只读 PROT_READ。但是 strtok() 修改了它的第一个参数 src,并得到了一个分段错误。您需要在使用 strtok 之前制作一个可写副本,或者切换到只读取其输入的机制。在我看来,将该缓冲区的保护更改为 PROT_RW 似乎很奇怪,特别是如果您打算在程序的其他地方使用该文件的未修改内容。

作为替代方案,我建议使用 strstr()(或不需要空字节终止的替代实现)来定位行尾子字符串,然后开始下一次搜索你找到最后一次出现的地方,加上你的子串的长度。请参阅下面有关空字节终止的注释。一个简化的例子:

  const char *delim = "\n";                                                                                                                          
  const char *start = src;                                                                                                                           
  const char *end = NULL;                                                                                                                            
  const int srclen = statbuf.st_size;                                                                                                                
  const int delim_length = strlen(delim);                                                                                                            

  while (start && start < (src + srclen)) {                                                                                                          
    end = strstr(start, delim);                                                                                                                      

    if (NULL == end) {  
      // use of %.* to print at most X chars from string.                                                                                                                             
      printf("Token: %.*s\n", (int) (src + srclen - start), start);                                                                                 
      break;                                                                                                                                         
    } else {                                                                                                                                         
      printf("Token: %.*s\n", (int) (end - start), start);                                                                                           
      start = end + delim_length;                                                                                                                    
    }                                                                                                                                                
  }                     

mmap 区域不能以空字节结尾(如评论所建议)

strstr() 适用于以 null 结尾的字符串。您映射的区域不能以空字节结尾。内核很可能会用 [=18=]s 擦除最后一个映射内存页面的剩余部分(超过文件末尾)以避免进程之间的数据泄漏,但如果您的文件长度恰好是页面的倍数-size,你在使用 strstr() 时会遇到麻烦——不会有空字节来让你回来。

您可以推出自己的小字符串查找器 strnstr()。或者强行在最后打上另一个空页。

strtok() 修改它操作的字符串。假设您不想更改文件内容,则需要更改 mmap() 选项。

您正在以只读方式打开和映射文件:

if ((fdsrc = open("filename.txt", O_RDONLY)) < 0) {
...
if ((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdsrc, 0)) == (caddr_t) -1) {
...

将文件映射到 PROT_READ|PROT_WRITEMAP_PRIVATE:

src = mmap(0, statbuf.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fdsrc, 0);
if (src == (caddr_t) -1) {

您可能需要使用 O_RDWR 而不是 O_RDONLY

打开文件

注意事项:

如果文件大小与用于映射的页面大小的倍数完全匹配,则文件将不是以 NUL 结尾的字符串,并且当 strtok() 尝试读取超过映射结束。

在这种情况下,您可以 mmap() 紧跟在文件映射之后的零填充页面。