【问题标题】:Why does my string_split implementation not work?为什么我的 string_split 实现不起作用?
【发布时间】:2019-03-25 23:11:55
【问题描述】:

我的str_split 函数返回(或者至少我认为是这样)char** - 所以本质上是一个字符串列表。它需要一个字符串参数、一个 char 分隔符来分割字符串,以及一个指向 int 的指针来放置检测到的字符串数。

我这样做的方式可能非常低效,是创建一个 x 长度的缓冲区(x = 字符串长度),然后复制字符串的元素,直到我们到达分隔符或 '\0' 字符。然后它将缓冲区复制到char**,这就是我们要返回的内容(之前已经被malloced,并且可以从main() 中释放),然后清除缓冲区并重复。

虽然算法可能有问题,但逻辑绝对是合理的,因为我的调试代码(_D)显示它被正确复制。我坚持的部分是当我在main 中创建char** 时,将其设置为等于我的功能。它不会返回 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;
}

【问题讨论】:

  • "然后它将缓冲区复制到 char**" - 不,它没有。在哪里?
  • 在:ret[c] = 缓冲区;我错过了一些非常明显的东西吗?对不起!编辑:如果您指的是副本的使用,我可以互换使用“副本”和“设置”。就像设置 char**[index] 的值等于缓冲区一样。
  • 这不会复制缓冲区。 ret[c] 只是一个指针。您将它设置为指向buffer,这是一个局部变量,当周围的函数返回时它会被销毁。此外,ret 的所有元素都具有相同的值 (buffer)。您正在返回一个包含相同垃圾指针的数组。
  • 您正在存储指向堆栈中存在的缓冲区的指针。从函数返回后使用这些指针会导致未定义的行为。
  • @melpomene 啊!我想它是那样的,但我无法用我的一生来形容它。非常感谢先生。那么你会如何建议我从这里开始 - 有没有其他方法可以分别 malloc'ing 每个字符串?当然,我永远无法释放他们。

标签: c arrays pointers split malloc


【解决方案1】:

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

要解决此问题,需要以下条件之一:

  • 允许函数修改输入字符串(即用空终止符替换分隔符)并返回指向它的指针。调用者必须意识到这可能发生。请注意,在 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,因为所有内容都存储在一起。

【讨论】:

  • 非常感谢您的帮助!我仍在通读它并试图理解,但我认为这里是完美的。我对你在 2 中给出的例子有点困惑。但我相信我最终会明白的。
  • 我意识到我有一个小错误,所以请查看我的编辑。循环非常基本。它只是使用char *p 遍历字符串中的每个字符,使buffer 指向当前正在搜索的子字符串的开头。当它找到分隔符(或字符串结尾)时,它会终止该子字符串,将buffer 指针附加到您的数组,然后将buffer 指向下一个子字符串的开头(如果有)。当buffer 指向一个空字符串时,循环终止,一旦提取了所有字符串(包括没有,如果原始字符串为空),就会出现这种情况。
【解决方案2】:

您存储到resret[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;
}

【讨论】:

    猜你喜欢
    • 2022-01-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-10
    • 2017-10-18
    • 2015-05-03
    • 1970-01-01
    相关资源
    最近更新 更多