使用memmem进行字符串搜索失败

3
我不喜欢只是把一堆代码倒在这里,然后让别人为我调试,但我在C语言方面有点缺乏经验,完全被卡住了。
总体目标是对一个非常大的日志文件(11G+)进行清理,我每次读取2048个字节,然后扫描每行,在写入输出文件。最初我使用strstr查找行末,但我发现在读缓冲区末尾的部分行时,它无法工作-我认为这是因为我从文件中读取的“字符串”没有\0结尾,导致strstr混淆了。
所以,经过一番搜索,我想尝试memmem,它似乎是strstr的“二进制安全”替代品。这就是我的问题所在,当调用memmem时,我的程序会崩溃(segmentation fault)。
#include <stdio.h>
#include <string.h>

#define BUFF_LEN 2048

int main (void)
{
    char file_buff[BUFF_LEN], prev_line[BUFF_LEN], curr_line[BUFF_LEN];
    char *p_line_start, *p_lf;
    int bytes_consumed, bytes_read;
    FILE *in_fp, *out_fp;

    in_fp = fopen("208.log", "r");
    out_fp = fopen("expanded.log", "w+");

    int sane = 0;
    while (1) {
        bytes_read = fread(file_buff, 1, BUFF_LEN, in_fp);
        if (bytes_read == 0) {
            break;
        }

        // Set the pointer to the beginning of the file buffer
        p_line_start = file_buff;
        bytes_consumed = 0;

        // Chomp lines
        while (bytes_consumed < bytes_read) {
            printf("Read to go with bytes_read = %d, bytes_consumed = %d\n",
                bytes_read, bytes_consumed);
            p_lf = (char *) memmem(p_line_start, bytes_read - bytes_consumed,
                "\n", 1);
            if (p_lf == NULL) {
                // No newline left in file_buff, store what's left in
                // curr_line and break out to read more from the file.
                printf("At loop exit I have chomped %ld of %d\n",
                    p_line_start - file_buff, bytes_read);
                //break;
                goto cleanup;
            }
            // Copy the line to our current line buffer (including the newline)
            memcpy(curr_line, p_line_start, p_lf - p_line_start + 1);
            printf("Chomped a line of length %ld\n", p_lf - p_line_start + 1);
            fwrite(curr_line, 1, p_lf - p_line_start + 1, out_fp);
            p_line_start = p_lf + 1;
            bytes_consumed += p_lf - p_line_start + 1;
        }

有人能给我支个招吗?!
欢迎提供如何更好地自行调试的技巧。


1
你使用fread而不是fgets读取文件,使事情变得过于复杂。当然,在使用fgets时,你可能仍需要处理行截断。输入文件中最长的行有多长?或者这是未知的? - user3386109
@nneonneo - 谢谢你的建议 - 我会去研究一下。 - Robin
@Thomas Padron-McCarthy - 是的,我已经通过gdb运行了它,但崩溃发生在memmem函数内部。 - Robin
1
我认为文件I/O驱动程序会以大块方式读取文件,所以你不太可能看到freadfgets之间的主要性能差异。至于最长的行,有一个5MB的行缓冲区并不算不合理,例如 char *buffer = malloc(5000000); while (fread(buffer,5000000,fp_in)!=NULL){...} 那么我的问题是,您是否预期日志文件将具有超过5MB的行? - user3386109
1
@indiv:这是可能的,而且它可以使用至少与搜索字符串一样长的缓冲区(可能更短——尽管我对此不太确定),但是管理开销很大,容易出错,并且难以调试。我也会选择一个较大的单行输入缓冲区。然后将其大小加倍,只是为了安全起见。 - Jongware
显示剩余8条评论
1个回答

2

从您的评论中可以看出:

我转换返回值是因为gcc会报警告:"warning: assignment makes pointer from integer without a cast".

您只是通过转换返回值来隐藏问题。

memmem 返回一个指针。通常情况下,指针是64位的。如果您没有声明该函数,则编译器不知道它返回一个指针,而是假定它返回一个整数。通常情况下,一个整数是32位的。生成的代码将查找应返回整数的位置,并从那里获取32位。实际上,它将获得返回指针的一半。

尝试在调用 memmem 后添加以下行,并查看是否声明或未声明 memmem 时打印输出结果有所不同:

printf("[p_lf = %p]\n", (void*)p_lf);

当我运行原始程序(没有声明)时,它打印了0xffffffffffffda67,然后崩溃了,因为那是一个无效的指针。使用声明(使用#define _GNU_SOURCE),它打印了0x7fffffffda67,并且没有崩溃。
请注意,如果只取0x7fffffffda67的32位低位,则得到0xffffda67,如果再将其扩展到64位,则得到来自原始程序的指针0xffffffffffffda67。(关闭地址空间布局随机化。)
这就是为什么不应该强制转换返回值的原因。

有趣的是,man memmem 指示需定义_GNU_SOURCE,但实际上该函数只在定义了 __USE_GNU时才被声明。IDE中的符号高亮和弹出语法提示更让情况变得更加混乱,给用户带来了一种错误的安全感。 - Anton Samsonov

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