我需要释放strtok生成的字符串吗?

24

更确切地说,strtok如何生成其返回值所指向的字符串?它是否动态分配内存?我之所以问这个问题是因为我不确定在下面的代码中是否需要释放标记:

STANDARD_INPUT变量用于退出过程,以防我在分配内存时耗尽内存,而string是被测试的主题。

int ValidTotal(STANDARD_INPUT, char *str)
{
    char *cutout = NULL, *temp, delim = '#';
    int i = 0; //Checks the number of ladders in a string, 3 is the required number
    temp = (char*)calloc(strlen(str),sizeof(char));
    if(NULL == temp)
        Pexit(STANDARD_C); //Exit function, frees the memory given in STANDARD_INPUT(STANDARD_C is defined as the names given in STANDARD_INPUT)
    strcpy(temp,str);//Do not want to touch the actual string, so copying it
    cutout = strtok(temp,&delim);//Here is the lynchpin - 
    while(NULL != cutout)
    {
        if(cutout[strlen(cutout) - 1] == '_')
            cutout[strlen(cutout) - 1] = '\0'; \\cutout the _ at the end of a token
        if(Valid(cutout,i++) == INVALID) //Checks validity for substring, INVALID is -1
            return INVALID;
        cutout = strtok(NULL,&delim);
        strcpy(cutout,cutout + 1); //cutout the _ at the beginning of a token
    }
    free(temp);
return VALID; // VALID is 1
}

1
不需要这样做,因为它返回了temp部分的地址,与strtok代码相关。temp可以通过free(temp)释放。 - BLUEPIXY
4个回答

20

strtok函数操作您传入的字符串并返回指向该字符串的指针,因此不会分配内存。

请考虑使用strsep或至少strtok_r来避免后续出现问题。


2
strtok 不是可重入的。 - Andreas

9
strtok(...)函数的第一个参数是您的字符串:

str
要截取的C字符串。请注意,该字符串会被修改并被分解成较小的字符串(标记)。或者可以指定一个空指针,在这种情况下,函数将继续扫描先前成功调用函数的位置。

它在您的字符串中放置'\0'字符,并将其作为终止的字符串返回。是的,它会损坏您的原始字符串。如果您以后需要使用,请复制一份。 此外,它不应该是常量字符串(例如char* myStr = "constant string";)。请看这里
它可以在本地分配或通过malloc/calloc进行分配。
如果您在堆栈上本地分配了它(例如char myStr[100];),则无需释放它。
如果您通过malloc(例如char* myStr = malloc(100*sizeof(char));)进行分配,则需要释放它。
以下是一些示例代码:
#include <string.h>
#include <stdio.h>
int main()
{
   const char str[80] = "This is an example string.";
   const char s[2] = " ";
   char *token;

   /* get the first token */
   token = strtok(str, s);

   /* walk through other tokens */
   while( token != NULL ) 
   {
      printf( " %s\n", token );

      token = strtok(NULL, s);
   }

   return(0);
}

注意:此示例展示如何迭代字符串...由于原始字符串已被篡改,strtok(...)会记住您上次的位置并继续处理该字符串。


7
根据文档
返回值
指向在字符串中找到的最后一个标记的指针。
由于返回指针只是指向输入字符串中标记开始的其中一个字节,因此是否需要释放取决于您是否分配了输入字符串。

0
正如其他人所提到的,strtok使用第一个参数——您的输入字符串作为内存缓冲区。它不会分配任何东西。它是有状态的且不安全;如果strtok的第一个参数为空,则重用先前提供的缓冲区。在调用期间,strtok破坏字符串,在其中添加空值并返回指向标记的指针。

以下是一个示例:

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

int main() {
    char s[] = "foo;bar;baz";
    char *foo = strtok(s, ";");
    char *bar = strtok(NULL, ";");
    char *baz = strtok(NULL, ";");

    printf("%s %s %s\n", foo, bar, baz); // => foo bar baz
    printf("original: %s\n", s); // => original: foo
    printf("%ssame memory loc\n", s == foo ? "" : "not "); // => same memory loc

    return 0;
}

s 最初是 foo;bar;baz\0。三次调用 strtok 将其变成了 foo\0bar\0baz\0s 基本上与第一块 foo 相同。

Valgrind:

==89== HEAP SUMMARY:
==89==     in use at exit: 0 bytes in 0 blocks
==89==   total heap usage: 1 allocs, 1 frees, 1,024 bytes allocated
==89==
==89== All heap blocks were freed -- no leaks are possible

虽然下面的代码并不能解决所有与 strtok 相关的问题,但它可能会在紧急情况下帮助您前进,并使用 strdup 保留原始字符串:

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

int main() {
    const char s[] = "foo;bar_baz";
    const char delims[] = ";_";
    char *cpy = strdup(s);
    char *foo = strtok(cpy, delims);
    char *bar = strtok(NULL, delims);
    char *baz = strtok(NULL, delims);

    printf("%s %s %s\n", foo, bar, baz); // => foo bar baz
    printf("original: %s\n", s); // => original: foo;bar_baz
    printf("%ssame memory loc\n", s == foo ? "" : "not "); // => not same memory loc

    free(cpy);
    return 0;
}

或者一个更完整的例子(仍然不是线程安全的):

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

void must(
    bool predicate,
    const char *msg,
    const char *file,
    const unsigned int line
) {
    if (!predicate) {
        fprintf(stderr, "%s:%d: %s\n", file, line, msg);
        exit(1);
    }
}

size_t split(
    char ***tokens,
    const size_t len,
    const char *s,
    const char *delims
) {
    char temp[len+1];
    temp[0] = '\0';
    strcpy(temp, s);
    *tokens = malloc(sizeof(**tokens) * 1);
    must(*tokens, "malloc failed", __FILE__, __LINE__);
    size_t chunks = 0;

    for (;;) {
        char *p = strtok(chunks == 0 ? temp : NULL, delims);

        if (!p) {
            break;
        }

        size_t sz = sizeof(**tokens) * (chunks + 1);
        *tokens = realloc(*tokens, sz);
        must(*tokens, "realloc failed", __FILE__, __LINE__);
        (*tokens)[chunks++] = strdup(p);
    }

    return chunks;
}

int main() {
    const char s[] = "foo;;bar_baz";
    char **tokens;
    size_t len = split(&tokens, strlen(s), s, ";_");

    for (size_t i = 0; i < len; i++) {
        printf("%s ", tokens[i]);
        free(tokens[i]);
    }

    puts("");
    free(tokens);
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接