为什么我的string_split实现不起作用?

时间:2019-03-25 23:11:56

标签: c arrays pointers split malloc

我的str_split函数返回(或至少我认为是)char**-因此本质上是一个字符串列表。它需要一个字符串参数,一个用于分隔字符串的char分隔符和一个指向int的指针来放置检测到的字符串数。

我这样做的方法可能效率很低,它是制作一个x长度(x =字符串长度)的缓冲区,然后复制字符串元素,直到到达定界符或'\0'字符为止。然后,它将缓冲区复制到我们正在返回的char**中(先前已malloc被释放,可以从main()中释放),然后清除缓冲区并重复执行。

尽管该算法可能比较困难,但是由于我的调试代码(_D)表明它已正确复制,因此逻辑肯定是合理的。我被困住的部分是当我在char**中创建一个main时,将其设置为等于我的函数。它不会返回null,不会使程序崩溃或引发任何错误,但是似乎也不起作用。我假设这是术语未定义行为。

无论如何,经过一番思考(我是所有这一切的新手),我尝试了其他一些事情,您将在代码中看到这些东西,目前已注释掉。当我使用malloc将缓冲区复制到新字符串,并将该副本传递给上述char **时,它似乎运行得很好。但是,这会造成明显的内存泄漏,因为我以后无法释放它……所以我迷路了。

当我做一些研究时,发现this post几乎完全遵循我的代码思想并且可以正常工作,这意味着str_split的格式(返回值,参数等)没有固有的问题。功能。到目前为止,他只有1个malloc用于char **,并且工作正常。

下面是我的代码。我一直在试图弄清楚这个问题,它使我的大脑混乱不堪,所以我非常感谢帮助!!事先对“ i”,“ b”,“ c”表示抱歉,我知道这有点令人费解。

编辑:应使用以下代码进行提示,

ret[c] = buffer;
printf("Content of ret[%i] = \"%s\" \n", c, ret[c]);

它确实可以正确打印。只有当我从main调用函数时,它才会变得怪异。我猜是因为它超出范围了?

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

#define DEBUG

#ifdef DEBUG
    #define _D if (1)
#else
    #define _D if (0)
#endif

char **str_split(char[], char, int*);
int count_char(char[], char);

int main(void) {
    int num_strings = 0;
    char **result = str_split("Helo_World_poopy_pants", '_', &num_strings);

    if (result == NULL) {
        printf("result is NULL\n");
        return 0;
    }

    if (num_strings > 0) {
        for (int i = 0; i < num_strings; i++) {
            printf("\"%s\" \n", result[i]);
        }
    }

    free(result);

    return 0;
}

char **str_split(char string[], char delim, int *num_strings) {

    int num_delim = count_char(string, delim);
    *num_strings = num_delim + 1;

    if (*num_strings < 2) {
        return NULL;
    }

    //return value
    char **ret = malloc((*num_strings) * sizeof(char*));

    if (ret == NULL) {
        _D printf("ret is null.\n");
        return NULL;
    }

    int slen = strlen(string);
    char buffer[slen];

    /* b is the buffer index, c is the index for **ret */
    int b = 0, c = 0;
    for (int i = 0; i < slen + 1; i++) { 

        char cur = string[i];

        if (cur == delim || cur == '\0') {

            _D printf("Copying content of buffer to ret[%i]\n", c); 
            //char *tmp = malloc(sizeof(char) * slen  + 1);
            //strcpy(tmp, buffer);

            //ret[c] = tmp;
            ret[c] = buffer;
            _D printf("Content of ret[%i] = \"%s\" \n", c, ret[c]);
            //free(tmp);

            c++;
            b = 0;
            continue;
        }

        //otherwise

        _D printf("{%i} Copying char[%c] to index [%i] of buffer\n", c, cur, b);

        buffer[b] = cur;
        buffer[b+1] = '\0'; /* extend the null char */
        b++;

        _D printf("Buffer is now equal to: \"%s\"\n", buffer);
    }

    return ret;
}

int count_char(char base[], char c) {
    int count = 0;
    int i = 0;

    while (base[i] != '\0') {
        if (base[i++] == c) {
            count++;
        }
    }
    _D printf("Found %i occurence(s) of '%c'\n", count, c);
    return count;
}

2 个答案:

答案 0 :(得分:0)

使用res数组存储在ret[c] = buffer;中的字符串指针指向一个自动数组,该函数在函数返回时会超出范围。该代码随后具有未定义的行为。您应该使用strdup()分配这些字符串。

还请注意,当字符串不包含分隔符时,返回NULL是不合适的。为什么不返回带有单个字符串的数组?

这是一个更简单的实现:

#include <stdlib.h>

char **str_split(const char *string, char delim, int *num_strings) {
    int i, n, from, to;
    char **res;

    for (n = 1, i = 0; string[i]; i++)
        n += (string[i] == delim);

    *num_strings = 0;
    res = malloc(sizeof(*res) * n);
    if (res == NULL)
        return NULL;

    for (i = from = to = 0;; from = to + 1) {
        for (to = from; string[to] != delim && string[to] != '\0'; to++)
            continue;
        res[i] = malloc(to - from + 1);
        if (res[i] == NULL) {
            /* allocation failure: free memory allocated so far */
            while (i > 0)
                free(res[--i]);
            free(res);
            return NULL;
        }
        memcpy(res[i], string + from, to - from);
        res[i][to - from] = '\0';
        i++;
        if (string[to] == '\0')
            break;
    }
    *num_strings = n;
    return res;
}

答案 1 :(得分:0)

您正在存储指向堆栈上存在的缓冲区的指针。从函数返回后使用这些指针会导致未定义的行为。

要解决此问题,需要执行以下操作之一:

  • 允许该函数修改输入字符串(即用空终止符替换定界符)并将指针返回到其中。呼叫者必须意识到这可能发生。请注意,按照此处的操作提供字符串文字在C语言中是非法的,因此您需要这样做:

    char my_string[] = "Helo_World_poopy_pants";
    char **result = str_split(my_string, '_', &num_strings);
    

    在这种情况下,函数还应明确指出字符串文字是不可接受的输入,并将其第一个参数定义为const char* string(而不是char string[])。

  • 允许该函数创建字符串的副本,然后修改副本。您已经表达了对泄漏此内存的担忧,但是这种担忧主要与程序的设计有关,而不是必需的。

    单独复制每个字符串,然后稍后将它们全部清除是完全有效的。主要问题是它不方便,而且也毫无意义。

让我们谈第二点。您有几种选择,但是如果您坚持要通过调用free轻松清理结果,请尝试以下策略:

  1. 分配指针数组时,还应使其足够大以容纳字符串的副本:

    // Allocate storage for `num_strings` pointers, plus a copy of the original string,
    // then copy the string into memory immediately following the pointer storage.
    char **ret = malloc((*num_strings) * sizeof(char*) + strlen(string) + 1);
    char *buffer = (char*)&ret[*num_strings];
    strcpy(buffer, string);
    
  2. 现在,在buffer上执行所有字符串操作。例如:

    // Extract all delimited substrings.  Here, buffer will always point at the
    // current substring, and p will search for the delimiter.  Once found,
    // the substring is terminated, its pointer appended to the substring array,
    // and then buffer is pointed at the next substring, if any.
    int c = 0;
    for(char *p = buffer; *buffer; ++p)
    {
        if (*p == delim || !*p) {
           char *next = p;
           if (*p) {
               *p = '\0';
               ++next;
           }
           ret[c++] = buffer;
           buffer = next;
        }
    }
    
  3. 当您需要清理时,只需将其free调用一次,因为所有内容都存储在一起了。