2015-11-24 37 views
3

这里是我的问题:我要地图文件“FILENAME.TXT”,基本上由两对每行字符串的:用strtok解析mmaped文件?

"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呢?

+3

strtok是*破坏性的 - 它写入字符串,因为它标记它。解决方案:1)在尝试“strtok()”之前将每个字符串复制到本地缓冲区,或者2)打开映射读/写(而不是只读)。 – paulsm4

回答

6

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

您正在打开和映射文件设置为只读:

地图文件与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()尝试读取超过映射末尾时,您可能会收到SIGSEGV。

在这种情况下,您可以在文件映射后立即填入mmap()零填充页。

+1

这看起来像不好的建议。该文件将被修改,可能不是OP的意图。 – chqrlie

+0

@chqrlie错了。根据http://man7.org/linux/man-pages/man2/mmap.2.html:** MAP_PRIVATE ** *创建一个私人写时复制映射。映射 映射的更新对于映射相同 文件的其他进程是不可见的**,并且不会传递到底层文件**。 ... * –

+1

我的不好,你是对的。但我坚持认为它仍然是一个扭曲的方法来解析文本文件。具体而言,不应该使用'strtok()',因为在映射结束之前缓冲区可能不是终止的,因此调用未定义的行为,例如,如果文件长度是页面大小的精确倍数。 – chqrlie

2

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

mmap该文件处于只读模式,因此您在strtok尝试修改内存时出现违规。

mmap该文件在读写模式下将是一个坏主意,该文件将被修改并可能损坏。

strtok不适合你的目的,编写你自己的匹配函数,它不修改它的参数数组并返回偏移和长度。

另外要注意的是,mmap PED内存不应超出文件的大小进行访问,并且不必'\0'终止,因此你不应该使用字符串函数来搜索到它(strchrstrstrstrlen ...)也不从其复制(strcpy)。

3

您的文件映射为只读用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区域可以不是在一个NUL字节结尾(由评论所建议的)

strstr()作品上空终止字符串。您的mmapped区域可能不会以空字节结尾。内核很可能会使用\0 s擦除最后一个mmapped内存页面的剩余部分(超过文件结尾),以避免进程之间的数据泄漏,但是如果文件长度恰好是页面大小的倍数,那么您在使用strstr()时会遇到问题 - 不会有空字节让你回来。

你可以推出自己的小字符串查找器strnstr()。或者强制另一个空白页面在最后加盖。

+1

你不应该使用'strstr',因为'mmap'ped缓冲区不一定是''\ 0''终止的。 – chqrlie

+0

谢谢@chqrlie。你是对的。这给了我一个小小的测试螺旋。 'seek()'过去EOF(返回0B)时允许'read()'调用,所以我想知道是什么要求mmap比文件更大的区域会产生效果。我最终发现(至少在linux上)的是,如果文件大小不是页面大小的倍数,则包含文件数据的最后一个缓冲页面将填充零。例如,将一个10B文件读入一个8MB mmap缓冲区(带4K页面)会使索引10到4095为零,但是如果读取索引4096和8MB之间的任何内容,则会发生总线错误(SIGBUS)。 –