免费,无效指针

Free, invalid pointer

我有一个程序,可以根据分隔符拆分字符串。我还有 2 个其他函数,一个打印返回的数组,另一个释放数组。

我的程序打印数组,returns调用自由数组方法时出现错误。以下是完整代码。

#include "stringsplit.h"

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

/* Split string by another string, return split parts + NULL in array.
 *
 * Parameters: 
 *  str: the string to split
 *  split: the string to split str with
 *
 * Returns:
 *  A dynamically reserved array of dynamically reserved string parts.
 *
 * For example called with "Test string split" and " ",
 * returns ["Test", "string", "split", NULL].
 * Or called with "Another - test" and " - ",
 * returns ["Another", "test", NULL].
 */

unsigned long int getNofTokens(const char *string) {
  char *stringCopy;
  unsigned long int stringLength;
  unsigned long int count = 0;

  stringLength = (unsigned)strlen(string);
  stringCopy = malloc((stringLength + 1) * sizeof(char));
  strcpy(stringCopy, string);

  if (strtok(stringCopy, " \t") != NULL) {
    count++;
    while (strtok(NULL, " \t") != NULL)
        count++;
  }

  free(stringCopy);
  return count;
}

char **split_string(const char *str, const char *split) {
    unsigned long int count = getNofTokens(str);
    char **result;
    result = malloc(sizeof(char *) * count + 1);
    
    char *tmp = malloc(sizeof(char) * strlen(str));
    strcpy(tmp, str);
    
    char *token = strtok(tmp, split);
    
    int idx = 0;
    while (token != NULL) {
        result[idx++] = token;
        token = strtok(NULL, split);
    }
    
    return result;
}

void print_split_string(char **split_string) {
    for (int i = 0; split_string[i] != NULL; i++) {
        printf("%s\n", split_string[i]);
    }
}

void free_split_string(char **split_string) {
    for (int i = 0; split_string[i] != NULL; i++) {
        char *currentPointer = split_string[i];
        free(currentPointer);
    }

    free(split_string);
}

此外,我需要在数组末尾显式添加 [=11=] 还是 strtok 自动添加?

调试时,记下您从 mallocstrdup 等获得的值。我们将这些值称为“活动集”。这只是一个名称,以便我们可以参考它们。你从这些函数中得到一个指针,你在心里把它添加到活动集中。当您调用 free 时,您可以 传递来自活动集的值,并且在释放 returns 之后,您可以将它们从集中删除。 free 的任何其他使用都是无效的,并且是一个错误。

您可以通过在所有内存分配之后放置断点轻松找到这一点,这样您就可以记下指针值,然后在所有释放上设置断点,这样您就可以查看这些指针值之一是否已传递给免费 - 因为,再一次,否则就是滥用 free.

这也可以使用“printf”调试来完成。像这样:

char *buf = malloc(...); // or strdup, or ...
fprintf(stderr, "+++ Alloc %8p\n", buf);

然后只要有空,再做一次:

fprintf(stderr, "--- Free  %8p\n", ptr);
free(ptr);

在程序的输出中,您必须能够将每个 +++--- 匹配。如果你看到任何 --- 的值之前没有用 +++ 列出,那就是你的问题:那是 free :)

的错误调用

我建议使用 fprintf(stderr, ... 而不是 printf(...,因为前者通常是无缓冲的,因此如果您的程序崩溃,您不会错过任何输出。 printf 在某些架构上进行了缓冲(而在其他架构上未进行缓冲 - 为了保持一致性)。

您的代码中存在一些问题:

  • [Major] 函数getNofTokens() 不以分隔符字符串为参数,它统计由空格分隔的单词数,可能会向其调用者返回不一致的计数。

  • [Major] result = malloc(sizeof(char *) * count + 1);中分配的大小不正确:应该是:

      result = malloc(sizeof(char *) * (count + 1));
    

    存储尾随 NULL 指针将超出分配的 space.

    的末尾
  • [Major] 在数组末尾存储所述 NULL 终止符确实是必要的,因为 malloc() 未初始化。

  • [Major]无法安全释放由split_string分配和解析的字符串副本,因为指针tmp没有保存在任何地方。在两种情况下,指向第一个标记的指针将不同于 tmp:如果字符串仅包含定界符(未找到标记)或者如果字符串以定界符开头(将跳过初始定界符)。为了简化代码并使其可靠,可以复制每个令牌并释放 tmp。事实上,您的 free_split_string() 函数依赖于此行为。对于当前的实现,行为是未定义的。

  • [Minor] 您对字符串长度和数组索引变量使用 unsigned longint 不一致。为了保持一致性,您应该对两者都使用 size_t

  • [备注] 你应该用 strdup() 分配字符串副本。如果此 POSIX 标准函数在您的系统上不可用,请编写一个简单的实现。

  • [主要] 你从不测试内存分配失败。这可以用于测试目的并丢弃代码,但应始终在生产代码中考虑此类潜在故障。

  • [备注] strtok() 是一个使用起来很棘手的函数:它修改源字符串并保持隐藏的静态状态,使其不-可重入。您应该避免使用此函数,尽管在这种特殊情况下它可以正确执行,但如果 split_stringgetNofTokens 的调用者依赖于保留此隐藏状态,则会出现意外行为。

这是修改后的版本:

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

#include "stringsplit.h"

/* Split string by another string, return split parts + NULL in array.
 *
 * Parameters: 
 *  str: the string to split
 *  split: the string to split str with
 *
 * Returns:
 *  A dynamically reserved array of dynamically reserved string parts.
 *
 * For example called with "Test string split" and " ",
 * returns ["Test", "string", "split", NULL].
 * Or called with "Another - test" and " - ",
 * returns ["Another", "test", NULL].
 */

size_t getNofTokens(const char *string, const char *split) {
    char *tmp = strdup(string);
    size_t count = 0;

    if (strtok(tmp, split) != NULL) {
        count++;
        while (strtok(NULL, split) != NULL)
            count++;
    }
    free(tmp);
    return count;
}

char **split_string(const char *str, const char *split) {
    size_t count = getNofTokens(str, split);
    char **result = malloc(sizeof(*result) * (count + 1));
    
    char *tmp = strdup(str);
    char *token = strtok(tmp, split);
    
    size_t idx = 0;
    while (token != NULL && idx < count) {
        result[idx++] = strdup(token);
        token = strtok(NULL, split);
    }
    result[idx] = NULL;
    
    free(tmp);
    return result;
}

void print_split_string(char **split_string) {
    for (size_t i = 0; split_string[i] != NULL; i++) {
        printf("%s\n", split_string[i]);
    }
}

void free_split_string(char **split_string) {
    for (size_t i = 0; split_string[i] != NULL; i++) {
        free(split_string[i]);
    }
    free(split_string);
}

这是一个没有 strtok() 且没有中间分配的替代方案:

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

#include "stringsplit.h"

size_t getNofTokens(const char *str, const char *split) {
    size_t count = 0;
    size_t pos = 0, len;

    for (pos = 0;; pos += len) {
        pos += strspn(str + pos, split);  // skip delimiters
        len = strcspn(str + pos, split);  // parse token
        if (len == '[=12=]')
            break;
        count++;
    }
    return count;
}

char **split_string(const char *str, const char *split) {
    size_t count = getNofTokens(str, split);
    char **result = malloc(sizeof(*result) * (count + 1));
    size_t pos, len, idx;

    for (pos = 0, idx = 0; idx < count; pos += len, idx++) {
        pos += strspn(str + pos, split);  // skip delimiters
        len = strcspn(str + pos, split);  // parse token
        if (len == '[=12=]')
            break;
        result[idx] = strndup(str + pos, len);
    }
    result[idx] = NULL;
    return result;
}

void print_split_string(char **split_string) {
    for (size_t i = 0; split_string[i] != NULL; i++) {
        printf("%s\n", split_string[i]);
    }
}

void free_split_string(char **split_string) {
    for (size_t i = 0; split_string[i] != NULL; i++) {
        free(split_string[i]);
    }
    free(split_string);
}

EDIT 在重新阅读您评论中的规范后,似乎对 split 参数的语义存在一些潜在的混淆:

  • 如果 split 是一组定界符,上面的代码就可以完成工作。示例将按预期拆分。
  • 如果 split 是要显式匹配的实际字符串,则上述代码仅适用于评论中给出的示例。

要实现后一种语义,您应该使用 strstr()getNofTokenssplit_string.

中搜索 split 子字符串

这是一个例子:

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

#include "stringsplit.h"

/* Split string by another string, return split parts + NULL in array.
 *
 * Parameters:
 *  str: the string to split
 *  split: the string to split str with
 *
 * Returns:
 *  A dynamically reserved array of dynamically reserved string parts.
 *
 * For example called with "Test string split" and " ",
 * returns ["Test", "string", "split", NULL].
 * Or called with "Another - test" and " - ",
 * returns ["Another", "test", NULL].
 */

size_t getNofTokens(const char *str, const char *split) {
    const char *p;
    size_t count = 1;
    size_t len = strlen(split);
    
    if (len == 0)
        return strlen(str);
    for (p = str; (p = strstr(p, split)) != NULL; p += len)
        count++;
    return count;
}

char **split_string(const char *str, const char *split) {
    size_t count = getNofTokens(str, split);
    char **result = malloc(sizeof(*result) * (count + 1));
    size_t len = strlen(split);
    size_t idx;
    const char *p = str;

    for (idx = 0; idx < count; idx++) {
        const char *q = strstr(p, split);
        if (q == NULL) {
            q = p + strlen(p);
        } else
        if (q == p && *q != '[=13=]') {
            q++;
        }
        result[idx] = strndup(p, q - p);
        p = q + len;
    }
    result[idx] = NULL;
    return result;
}

void print_split_string(char **split_string) {
    for (size_t i = 0; split_string[i] != NULL; i++) {
        printf("%s\n", split_string[i]);
    }
}

void free_split_string(char **split_string) {
    for (size_t i = 0; split_string[i] != NULL; i++) {
        free(split_string[i]);
    }
    free(split_string);
}