动态分配无法循环工作

时间:2019-10-16 00:04:14

标签: c loops debugging dynamic-memory-allocation string-parsing

我正在尝试将包含条形符号str的字符串|分解为字符串数组{output),分隔符为|,它将不包含在字符串数组中。 output中将有20个元素。此代码属于一个函数,该函数将返回output指针,这就是为什么我需要动态分配的原因。

我正在尝试使用sscanf()功能执行此操作。

示例:如果str"abc|def||jk",那么这就是output最后的样子(出于演示目的,少于20个元素):

  

output [0] ==“ abc”

     

output [1] ==“ def”

     

output [2] ==“”

     

output [3] ==“ jk”

但是,我总是收到错误退出代码,例如:

  

进程结束,退出代码为-1073740940(0xC0000374)

在调试时,我发现第一个字符串元素已正确解析为output,但是有时正确地生成了第二个元素,而有时我遇到了麻烦。

以下代码:

char **output = (char**) calloc(20, 20*sizeof(char));
int begin = 0;
int end = 1;
// index that counts output element
int arrayIndex = 0;
int i;
for(i = 0; i < strlen(str); i++) {
    end = i;
    bool endOfString = false;
    // there is no '|' symbol at the end of the string
    if (*(str+i) == '|' || (endOfString = (i+1)==strlen(str))) {
        end = endOfString ? (end+1):end;

        // problem here. Assembly code poped up when debugging (see image below)
        char *target = (char*) calloc(end-begin+1, sizeof(char));

        // give proper value to target
        strcpy(target, str+begin);
        *(target+end-begin) = '\0';
        *(output+arrayIndex) = target;
        // increase proper indexes
        begin = i + 1;
        arrayIndex++;

    }
}

最糟糕的是,我无法调试它,因为调试时我跳过calloc函数的实例会弹出带有汇编代码的窗口。 Assembly code window

我也使用了gdb,但是没有用:

  

56个字符 target =(char )calloc(length,sizeof(char));

     

(gdb)n

     

警告:检测到严重错误c0000374

     

线程1收到信号SIGTRAP,跟踪/断点陷阱。

     

0x00007ffded8191f3 in ?? ()

     

(gdb)bt

     

#0 0x00007ffded8191f3 in ?? ()

     

回溯停止:上一帧与此帧相同(损坏的堆栈?)

     

(gdb)继续

     

继续。

     

gdb:未知目标异常0xc0000374位于0x7ffded819269

1 个答案:

答案 0 :(得分:0)

(编辑)索引正常。几点注意事项:sizeof(char)始终为1-仅使用1。此外,不需要强制转换malloc的返回值,这是不必要的。请参阅:Do I cast the result of malloc?。另外,您叫strlen(str)多少次? (希望优化将循环条件限制为一个调用,但是您可能每次迭代都调用strlen(str))。与endOfString = (i+1)==strlen(str)相同。在进入循环之前保存字符串的长度,然后仅使用保存的值进行比较即可。

虽然您可以按自己的方式计算索引并逐个字符查找定界符,但让strchr (pointer, '|')前进到下一个定界符(或返回NULL表示没有更多限定符)效率更高。仍保留定界符)。然后,不必担心索引,只需保留指针p和结束指针ep即可向下推进字符串,例如

#define NFIELDS 20
...
char **splitstr (const char *s, const char delim, size_t *n)
{
    const char  *p = s,     /* pointer for parsing */
                *ep = s;    /* end pointer for parsing */
    *n = 0;                                 /* zero string counter */
    while (*n < NFIELDS && *p) {            /* loop while less than NFIELDS */
        size_t len;
        if ((ep = strchr (p, delim)))       /* get pointer to delim */
            len = ep - p;
        else
            len = strlen (p);               /* or get length of final string */
        if (!(output[*n] = malloc (len + 1))) { /* allocated for string */
        ...
        memcpy (output[*n], p, len);        /* copy chars to output[n] */
        output[(*n)++][len] = 0;            /* nul-terminate to make string */
        if (!ep)                            /* if delim not found, last */
            break;
        p = ++ep;                           /* update p to 1-past delim */
    }
    ...

添加适当的错误检查并将指针返回分配的字符串,您可以执行以下操作:

char **splitstr (const char *s, const char delim, size_t *n)
{
    const char  *p = s,     /* pointer for parsing */
                *ep = s;    /* end pointer for parsing */
    char **output = calloc (NFIELDS, sizeof *output);   /* pointer to output */

    if (!output) {
        perror ("calloc-output");
        return NULL;
    }

    *n = 0;                                 /* zero string counter */
    while (*n < NFIELDS && *p) {            /* loop while less than NFIELDS */
        size_t len;
        if ((ep = strchr (p, delim)))       /* get pointer to delim */
            len = ep - p;
        else
            len = strlen (p);               /* or get length of final string */
        if (!(output[*n] = malloc (len + 1))) { /* allocated for string */
            perror ("malloc-output[n]");
            while ((*n)--)          /* free prior allocations on failure */
                free (output[*n]);
            free(output);
            return NULL;
        }
        memcpy (output[*n], p, len);        /* copy chars to output[n] */
        output[(*n)++][len] = 0;            /* nul-terminate to make string */
        if (!ep)                            /* if delim not found, last */
            break;
        p = ++ep;                           /* update p to 1-past delim */
    }

    return output;      /* return pointer to allocated strings */
}

对于您而言,一个完整的简短示例将是:

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

#define NFIELDS 20
#define MAXC  1024

char **splitstr (const char *s, const char delim, size_t *n)
{
    const char  *p = s,     /* pointer for parsing */
                *ep = s;    /* end pointer for parsing */
    char **output = calloc (NFIELDS, sizeof *output);   /* pointer to output */

    if (!output) {
        perror ("calloc-output");
        return NULL;
    }

    *n = 0;                                 /* zero string counter */
    while (*n < NFIELDS && *p) {            /* loop while less than NFIELDS */
        size_t len;
        if ((ep = strchr (p, delim)))       /* get pointer to delim */
            len = ep - p;
        else
            len = strlen (p);               /* or get length of final string */
        if (!(output[*n] = malloc (len + 1))) { /* allocated for string */
            perror ("malloc-output[n]");
            while ((*n)--)          /* free prior allocations on failure */
                free (output[*n]);
            free(output);
            return NULL;
        }
        memcpy (output[*n], p, len);        /* copy chars to output[n] */
        output[(*n)++][len] = 0;            /* nul-terminate to make string */
        if (!ep)                            /* if delim not found, last */
            break;
        p = ++ep;                           /* update p to 1-past delim */
    }

    return output;      /* return pointer to allocated strings */
}

int main (void) {

    char buf[MAXC],         /* buffer for input */
        **output = NULL;    /* pointer to split/allocated strings */
    size_t n = 0;           /* number of strings filled */

    if (!fgets (buf, MAXC, stdin)) {    /* validate input */
        fputs ("error: invalid input.\n", stderr);
        return 1;
    }
    buf[strcspn (buf, "\n")] = 0;       /* trim newline from buf */
    /* split buf into separate strings on '|' */
    if (!(output = splitstr (buf, '|', &n))) {
        fputs ("error: splitstr() failed.\n", stderr);
        return 1;
    }

    for (size_t i = 0; i < n; i++) {    /* loop outputting each & free */
        printf ("output[%2zu]: %s\n", i, output[i]);
        free (output[i]);   /* free strings */
    }
    free (output);          /* free pointers */
}

使用/输出示例

$ echo "abc|def||jk" | ./bin/splitstr
output[ 0]: abc
output[ 1]: def
output[ 2]:
output[ 3]: jk

内存使用/错误检查

在您编写的任何动态分配内存的代码中,对于任何分配的内存块,您都有2个职责:(1)始终保留指向起始地址的指针因此,(2)当不再需要它时可以释放

当务之急是使用一个内存错误检查程序来确保您不会尝试访问内存或在已分配的块的边界之外/之外进行写入,不要试图以未初始化的值读取或基于条件跳转,最后,以确认您释放了已分配的所有内存。

对于Linux,valgrind是正常选择。每个平台都有类似的内存检查器。它们都很容易使用,只需通过它运行程序即可。

$ echo "abc|def||jk" | valgrind ./bin/splitstr
==32024== Memcheck, a memory error detector
==32024== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==32024== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info
==32024== Command: ./bin/splitstr
==32024==
output[ 0]: abc
output[ 1]: def
output[ 2]:
output[ 3]: jk
==32024==
==32024== HEAP SUMMARY:
==32024==     in use at exit: 0 bytes in 0 blocks
==32024==   total heap usage: 5 allocs, 5 frees, 172 bytes allocated
==32024==
==32024== All heap blocks were freed -- no leaks are possible
==32024==
==32024== For counts of detected and suppressed errors, rerun with: -v
==32024== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

始终确认已释放已分配的所有内存,并且没有内存错误。

仔细检查一下,如果还有其他问题,请告诉我。