使用strtok返回的细分错误

时间:2020-07-29 10:15:31

标签: c split segmentation-fault strtok

我正在尝试在c中创建一个函数,该函数像在Java或许多其他语言中的split函数一样,对字符串进行拆分。 我做了这个

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函数对字符串索引的操纵,但我无法理解如何解决它以及为什么它会给我带来分段错误。

2 个答案:

答案 0 :(得分:2)

一个问题是您打错了strtok

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

但是您没有传递定界符的字符串,而是传递了指向单个字符的指针。

因此将其更改为:

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

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

您的代码的另一个问题是您希望它始终返回至少两个有效令牌。这是一个错误的假设,它将在第二个代码示例的第二个循环中失败。

在第一个循环中,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);

它什么也没做,因此只需删除该行即可。

答案 1 :(得分:1)

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的原始缓冲区有效,它就是有效的。