在从文件崩溃中读取时使用realloc扩展缓冲区

时间:2012-01-23 14:26:30

标签: c realloc fasta

我正在编写一些需要阅读fasta files的代码,因此我的部分代码(包含在下面)是一个fasta解析器。由于单个序列可以跨越fasta格式的多行,我需要将从文件读取的多个连续行连接成一个字符串。我这样做,通过在读取每一行后重新分配字符串缓冲区,成为序列的当前长度加上读入的行的长度。我做了一些其他的东西,比如剥离空白等。一切顺利第一个序列,但fasta文件可以包含多个序列。所以类似地,我有一个动态的结构数组,有两个字符串(标题和实际序列),是“char *”。同样,当我遇到一个新标题(由以'>'开头的行引入)时,我增加序列数,并重新分配序列列表缓冲区。使用

为第二个序列分配空间的realloc段错误
*** glibc detected *** ./stackoverflow: malloc(): memory corruption: 0x09fd9210 ***
Aborted

对于我的生活,我不明白为什么。我通过gdb运行它,一切似乎都在工作(即一切都已初始化,值看起来很清晰)......这是代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#include <errno.h>

//a struture to keep a record of sequences read in from file, and their titles
typedef struct {
    char *title;
    char *sequence;
} sequence_rec;

//string convenience functions

//checks whether a string consists entirely of white space
int empty(const char *s) {
    int i;
    i = 0;
    while (s[i] != 0) {
        if (!isspace(s[i])) return 0;
        i++;
    }
    return 1;
}

//substr allocates and returns a new string which is a substring of s from i to
//j exclusive, where i < j; If i or j are negative they refer to distance from
//the end of the s
char *substr(const char *s, int i, int j) {
    char *ret;
    if (i < 0) i = strlen(s)-i;
    if (j < 0) j = strlen(s)-j;
    ret = malloc(j-i+1);
    strncpy(ret,s,j-i);
    return ret;
}

//strips white space from either end of the string
void strip(char **s) {
    int i, j, len;
    char *tmp = *s;
    len = strlen(*s);
    i = 0;
    while ((isspace(*(*s+i)))&&(i < len)) {
        i++;
    }
    j = strlen(*s)-1;
    while ((isspace(*(*s+j)))&&(j > 0)) {
        j--;
    }
    *s = strndup(*s+i, j-i);
    free(tmp);
}


int main(int argc, char**argv) {
    sequence_rec *sequences = NULL;
    FILE *f = NULL;
    char *line = NULL;
    size_t linelen;
    int rcount;
    int numsequences = 0;

    f = fopen(argv[1], "r");
    if (f == NULL) {
        fprintf(stderr, "Error opening %s: %s\n", argv[1], strerror(errno));
        return EXIT_FAILURE;
    }
    rcount = getline(&line, &linelen, f);
    while (rcount != -1) {
        while (empty(line)) rcount = getline(&line, &linelen, f);
        if (line[0] != '>') {
            fprintf(stderr,"Sequence input not in valid fasta format\n");
            return EXIT_FAILURE;
        }

        numsequences++;
        sequences = realloc(sequences,sizeof(sequence_rec)*numsequences);
        sequences[numsequences-1].title = strdup(line+1); strip(&sequences[numsequences-1].title);
        rcount = getline(&line, &linelen, f);
        sequences[numsequences-1].sequence = malloc(1); sequences[numsequences-1].sequence[0] = 0;
        while ((!empty(line))&&(line[0] != '>')) {
            strip(&line);
            sequences[numsequences-1].sequence = realloc(sequences[numsequences-1].sequence, strlen(sequences[numsequences-1].sequence)+strlen(line)+1);
            strcat(sequences[numsequences-1].sequence,line);
            rcount = getline(&line, &linelen, f);
        }
    }
    return EXIT_SUCCESS;
}

3 个答案:

答案 0 :(得分:4)

你应该使用看起来像这样的字符串:

struct string {
    int len;
    char *ptr;
};

这可以防止你看到的strncpy错误,并允许你更快地做strcat和朋友。

您还应该为每个字符串使用加倍数组。这可以防止过多的分配和memcpys。像这样:

int sstrcat(struct string *a, struct string *b)
{
    int len = a->len + b->len;
    int alen = a->len;
    if (a->len < len) {
        while (a->len < len) {
            a->len *= 2;
        }
        a->ptr = realloc(a->ptr, a->len);
        if (a->ptr == NULL) {
            return ENOMEM;
        }
    }
    memcpy(&a->ptr[alen], b->ptr, b->len);
    return 0;
}

我现在看到你正在做生物信息学,这意味着你可能需要比我想象的更多的表现。您应该使用这样的字符串:

struct string {
    int len;
    char ptr[0];
};

这样,当你分配一个字符串对象时,你调用malloc(sizeof(struct string) + len)并避免第二次调用malloc。这是一个更多的工作,但它应该在速度和内存碎片方面有所帮助。

最后,如果这实际上不是错误的来源,那么看起来你有一些腐败。如果gdb失败,Valgrind应该帮助你检测它。

答案 1 :(得分:3)

这里有一个潜在的问题:

strncpy(ret,s,j-i);
return ret;

ret可能无法获得null终止符。见man strncpy

       char *strncpy(char *dest, const char *src, size_t n);

       ...

       The strncpy() function is similar, except that at most n bytes  of  src
       are  copied.  Warning: If there is no null byte among the first n bytes
       of src, the string placed in dest will not be null terminated.

这里还有一个错误:

j = strlen(*s)-1;
while ((isspace(*(*s+j)))&&(j > 0)) {

如果strlen(*s)为0怎么办?您最终会阅读(*s)[-1]

您也不会检查strip()该字符串不完全由空格组成。如果是这样,您最终会得到j < i

编辑:注意到您的substr()函数实际上并没有被调用。

答案 2 :(得分:1)

我认为内存损坏问题可能是您处理getline()调用中使用的数据的结果。基本上,line会在strndup()的调用中通过strip()重新分配linelen,因此getline() getline()跟踪的缓冲区大小将不再准确。 while ((!empty(line))&&(line[0] != '>')) { strip(&line); // <-- assigns a `strndup()` allocation to `line` sequences[numsequences-1].sequence = realloc(sequences[numsequences-1].sequence, strlen(sequences[numsequences-1].sequence)+strlen(line)+1); strcat(sequences[numsequences-1].sequence,line); rcount = getline(&line, &linelen, f); // <-- the buffer `line` points to might be // smaller than `linelen` bytes } 可能会超出缓冲区。

{{1}}