C,分段错误解析大型csv文件

时间:2013-09-14 07:32:04

标签: c csv segmentation-fault

我写了一个简单的程序,它会打开一个csv文件,读取它,制作一个新的csv文件,并且只写一些列(我不想要所有的列,我希望删除一些会使文件成为更易于管理)。该文件为1.15GB,但fopen()没有问题。在第一次进展printf()之后不久,我的while循环中发生了分段错误。

我只测试了csv的前几行,下面的逻辑做了我想要的。 index == 0的最后一列是(xxx, yyy)\n形式的奇怪部分(逗号分隔值文件中的,是荒谬的。)

这是代码,while循环是问题:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv) {
    long size;
    FILE* inF = fopen("allCrimes.csv", "rb");
    if (!inF) {
        puts("fopen() error");
        return 0;
    }
    fseek(inF, 0, SEEK_END);
    size = ftell(inF);
    rewind(inF);

    printf("In file size = %ld bytes.\n", size);
    char* buf = malloc((size+1)*sizeof(char));
    if (fread(buf, 1, size, inF) != size) {
        puts("fread() error");
        return 0;
    }
    fclose(inF);
    buf[size] = '\0';

    FILE *outF = fopen("lessColumns.csv", "w");
    if (!outF) {
        puts("fopen() error");
        return 0;
    }

    int index = 0;
    char* currComma = strchr(buf, ',');
    fwrite(buf, 1, (int)(currComma-buf), outF);

    int progress = 0;
    while (currComma != NULL) {
        index++;
        index = (index%14 == 0) ? 0 : index;
        progress++;
        if (progress%1000 == 0) printf("%d\n", progress/1000);

        int start = (int)(currComma-buf);
        currComma = strchr(currComma+1, ',');
        if (!currComma) break;
        if ((index >= 3 && index <= 10) || index == 13) continue;

        int end = (int)(currComma-buf);
        int endMinusStart = end-start;
        char* newEntry = malloc((endMinusStart+1)*sizeof(char));
        strncpy(newEntry, buf+start, endMinusStart);
        newEntry[end+1] = '\0';

        if (index == 0) {
            char* findNewLine = strchr(newEntry, '\n');
            int newLinePos = (int)(findNewLine-newEntry);
            char* modifiedNewEntry = malloc((strlen(newEntry)-newLinePos+1)*sizeof(char));
            strcpy(modifiedNewEntry, newEntry+newLinePos);
            fwrite(modifiedNewEntry, 1, strlen(modifiedNewEntry), outF);
        }
        else fwrite(newEntry, 1, end-start, outF);
    }
    fclose(outF);

    return 0;
}

编辑:原来问题是csv文件在我没想到的地方,导致逻辑失败。我最终编写了一个新的解析器,删除了逗号数量不正确的行。它删除了243,875行(约占文件的4%)。我会发布该代码,因为它至少反映了有关free()的一些评论:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char** argv) {
    long size;
    FILE* inF = fopen("allCrimes.csv", "rb");
    if (!inF) {
        puts("fopen() error");
        return 0;
    }
    fseek(inF, 0, SEEK_END);
    size = ftell(inF);
    rewind(inF);

    printf("In file size = %ld bytes.\n", size);
    char* buf = malloc((size+1)*sizeof(char));
    if (fread(buf, 1, size, inF) != size) {
        puts("fread() error");
        return 0;
    }
    fclose(inF);
    buf[size] = '\0';

    FILE *outF = fopen("uniformCommaCount.csv", "w");
    if (!outF) {
        puts("fopen() error");
        return 0;
    }

    int numOmitted = 0;
    int start = 0;
    while (1) {
        char* currNewLine = strchr(buf+start, '\n');
        if (!currNewLine) {
            puts("Done");
            break;
        }

        int end = (int)(currNewLine-buf);
        char* entry = malloc((end-start+2)*sizeof(char));
        strncpy(entry, buf+start, end-start+1);
        entry[end-start+1] = '\0';

        int commaCount = 0;
        char* commaPointer = entry;
        for (; *commaPointer; commaPointer++) if (*commaPointer == ',') commaCount++;

        if (commaCount == 14) fwrite(entry, 1, end-start+1, outF);
        else numOmitted++;

        free(entry);
        start = end+1;
    }
    fclose(outF);
    printf("Omitted %d lines\n", numOmitted);

    return 0;
}

2 个答案:

答案 0 :(得分:1)

你是malloc'ing但从不自由。可能你用尽了memomry,你的一个mallocs返回NULL,然后调用str(n)cpy段错误。

在相应的free(newEntry);来电之后立即添加free(modifiedNewEntry);fwrite可以解决您的内存不足问题。

另请注意,在循环内部,您可以计算包含整个文件的缓冲区buf的偏移量。这些偏移量保存在int类型的变量中,其系统的最大值可能对于您正在处理的数字而言太小。另请注意,添加大int s可能会导致负值,这是segfault的另一个可能原因(负向偏移到buf会将您带到缓冲区外的某些地址,甚至可能无法读取)。

答案 1 :(得分:1)

malloc(3)功能可以(有时确实)失败。

至少代码类似

    char* buf = malloc(size+1);
    if (!buf) {
       fprintf(stderr, "failed to malloc %d bytes - %s\n", 
               size+1, strerror(errno));
       exit (EXIT_FAILURE);
    }

我强烈建议您使用memset(buf, 0, size+1)清除malloc(或以其他方式使用calloc ....)的成功结果,这不仅仅是因为以下fread可能会失败(您正在测试)但是为了简化调试和再现性。 同样对于malloccalloc的所有其他来电(你应该总是测试它们以防止失败)....

请注意,按照定义 sizeof(char) 总是 1.因此我删除了它。

正如其他人指出的那样,您有一个memory leak,因为您没有恰当地致电free。像valgrind这样的工具可以提供帮助。

您需要了解如何使用调试器(例如gdb)。不要忘记编译所有警告和调试信息(例如gcc -Wall -g)。并且在没有警告的情况下改进您的代码。

了解如何使用调试器是编程时必不可少的技能(特别是在C或C ++中)。调试技巧(以及使用调试器的能力)在您贡献的每个C或C ++程序中都很有用。

顺便说一句,你可以逐行读取你的文件getline(3)(也可能会失败,你应该测试一下)。