使用 strtok 返回分段错误

Segmentation Fault returned using strtok

我试图在 c 中创建一个函数,它像 java 或许多其他语言中的拆分函数一样拆分字符串。 我做了这个

char **split(char * str, char *ch) {
  char **array = (char **)malloc((strlen(str)) * sizeof(*array));
  int i = 0;
  char *token = strtok(str, ch);
  while (token != NULL) {
    array[i++] = token;
    token = strtok(NULL, ch);
  }
  free(token);
  return array;
}

这似乎有效,但并不总是有效,而且不正确。 假设我们以两种不同的方式调用它: 第一个工作的:

int main(){
  
  while(1){
    sleep(1);
    char h = ':';
    char a[] = "test:1234";
    char ** result = split(a,&h);
    printf("%s\n",result[0]);
    printf("%s\n",result[1]);
    free(result);
  }
}

而第二个在第二个 while 循环中给我一个分段错误:

int main(){
  char a[] = "test:1234";
  char h = ':';
  while(1){
    sleep(1);
    char ** result = split(a,&h);
    printf("%s\n",result[0]);
    printf("%s\n",result[1]);
    free(result);
  }
}

输出:

test
1234
test
Segmentation fault (core dumped)

我认为这是由于 strtok 函数对字符串索引进行了操作,但我不明白如何修复它以及它给我一个分段错误的确切原因。

一个问题是您调用 strtok 不正确。

strtok 需要两个字符串,即要拆分的字符串和一个分隔符字符串。

但是您没有传递 字符串 分隔符 - 您传递的是指向单个字符的指针。

所以改成这样:

char h = ':';                  --->  char *h = ":";

char ** result = split(a,&h);  --->  char ** result = split(a,h);

您的代码的另一个问题是您希望它总是 return 至少两个有效标记。这是一个错误的假设,它将在您的第二个代码示例的第二个循环中失败。

在第一个循环中,a 将更改为字符串“test”,因为 strtok':' 替换为字符串终止符。

在第二个循环中,因此只有一个标记。这意味着 result[1] 没有指向有效的标记,因此,您不能打印它指向的内容。

解决该问题的一种方法是在函数中将所有 result 指针设置为 NULL,例如通过使用 calloc 而不是 malloc 如:

char **array = calloc(strlen(str), sizeof(*array));

然后像这样打印:

if (result[0]) printf("%s\n",result[0]);
if (result[1]) printf("%s\n",result[1]);

或更好:

int i = 0;
while(result[i])
{
    printf("%s\n",result[i]);
    ++i;
}

综合起来:

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

char **split(char * str, char *ch) {
  char **array = calloc(strlen(str), sizeof(*array));  // Use calloc to set
                                                       // all pointers to NULL
  int i = 0;
  char *token = strtok(str, ch);
  while (token != NULL) {
    array[i++] = token;
    token = strtok(NULL, ch);
  }
  return array;
}

int main(){
  char a[] = "test:1234";
  char *h = ":";
  int z = 0;
  while(z < 5){    // Just loop 5 times
    //sleep(1);
    char ** result = split(a,h);
    int i = 0;
    while(result[i])   // Print all tokens, i.e. stop when a pointer is NULL
    {
        printf("%s\n",result[i]);
        ++i;
    }    
    free(result);
    ++z;
  }
}

输出:

test
1234
test
test
test
test

顺便说一句:

这个

free(token);

相同
free(NULL);

它什么都不做,所以只需删除该行。

strtok 使用起来有点棘手,因为它处理内存的方式不同 从习惯上来说-它修改作为参数传递的字符串,返回指向子字符串的指针,当 strtok(NULL,..) 时,一个新指针返回到缓冲区,如果缓冲区超出范围,则指针变为无效或者如果另一个线程正在调用 strtok 指针变得无效所以最好是在继续之前将返回的标记复制到另一个缓冲区之前。

这可以通过分配一个内存块然后复制进来来实现 那里的返回值

char **split(char * str, char *ch) {
  char **array = (char **)malloc((strlen(str)) * sizeof(*array));
  int i = 0;
  char *token = strtok(str, ch);
  while (token != NULL) {
    char* dupToken = malloc(strlen(token)+1);
    strcpy(dupToken, token);
    array[i++] = dupToken;
    token = strtok(NULL, ch);
  }
  // free(token); // this here is wrong
  return array;
}

现在你的代码的另一个问题是调用者无法知道 返回的数组中有多少标记,所以我建议另一种方法

一旦你击中最后一个标记,在返回之前将下一个指针设置为 NULL 数组

char **split(char * str, char *ch) {
  char **array = (char **)malloc((strlen(str)) * sizeof(*array));
  int i = 0;
  char *token = strtok(str, ch);
  while (token != NULL) {
    char* dupToken = malloc(strlen(token)+1);
    strcpy(dupToken, token);
    array[i++] = dupToken;
    token = strtok(NULL, ch);
  }
  array[i] = NULL;    
  return array;
}

这样当你遍历标记时你可以只检查指针

for (int i = 0; array[i] != NULL; ++i)
{ 
...
}

编辑:然后向您的数组添加另一个条目可能会很好,这样您就可以处理最大令牌数 + 1

char **array = (char **)malloc((strlen(str) + 1) * sizeof(*array));

编辑:更改了我对返回指针所发生情况的相当草率的描述,只要传递给 strtok 的原始缓冲区有效,它就有效。