【问题标题】:Returning pointer to char* ruins data返回指向 char* 的指针会破坏数据
【发布时间】:2021-05-07 04:10:14
【问题描述】:

我正在尝试编写一个函数,该函数将在不更改原始字符串的情况下沿空格 (' ') 拆分字符串,将所有标记放入数组中,然后返回该数组。我遇到的问题是返回指针。下面是我的代码。

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

char **split_line(char *ln) {
    char **tokens, *tok, line[256];
    int j;

    strcpy(line, ln);

    tokens = calloc(64, sizeof(char*));
    for (int i = 0; i < 64; i++)
        tokens[i] = calloc(64, sizeof(char));

    tokens[0] = strtok(line, " ");
    for (j = 1; (tok = strtok(NULL, " ")) != NULL && j < 63; j++) 
        tokens[j] = tok;
    tokens[j] = NULL;

    return tokens;
}

int main(void) {
    char **cut_ln, *input;

    input = "Each word in this string should be an element in cut_ln.";
    cut_ln = split_line(input);

    printf("`%s`\n", input);
    for (int i = 0; cut_ln[i] != NULL; i++)
        printf("[%d]: `%s`\n", i, cut_ln[i]);

    return 0;
}

运行时,会给出:

`This word in this string should be an element in cut_ln.`
[0]: `This`
[1]: `wo1`
[2]: `�={G+s`
[3]: `G+s`
[4]: `string`
[5]: ``
[6]: `0����`
[7]: `��`
[8]: ``
[9]: ``
[10]: ``

当我尝试在split_line 函数中打印tokens 的内容时,它给出了预期的结果。但是,当 tokens 返回并分配给一个变量,然后打印时,它会给出如上所示的结果。我做错了什么?

【问题讨论】:

  • tokens[j] = tok; 覆盖了您用calloc 仔细分配的指针,导致内存泄漏。您是否打算改为写 strcpy(tokens[j], tok) 之类的东西?
  • strdup() 然后strok()。不要重新发明轮子。如果您确实要重新发明它,请至少在快速扫描中先计算条目数,然后分配,然后返回。不要只是假设 64 就足够了。那几乎总是错误的。号码64 绝对没有理由出现在这里,你没有什么可以支持这个号码存在的原因。似乎是随意选择的。
  • 我赞扬你编写这个函数——它是处理字符串输入的好方法。很高兴在您的工具包中拥有。 (我希望有这样的标准库函数。)另见this question
  • @tadman 不知道strdup。但是,it would appear 它不是 ISO C 的一部分。
  • 除非你受到 ISO C 的特别限制,比如你在一些有限的平台上,那么可以肯定,但除此之外,一定程度的 POSIX 合规性实际上是给定的,即使在不符合 POSIX 的情况下也是如此Windows 等平台。当我们谈论现代 C 时,我们通常包括 POSIX,因为它随处可见。 GCC、clang 和 VCC 都支持它。一些用于长期死机操作系统的神秘 C89 编译器可能不会,就像我不会指望它出现在 Microsoft XENIX 中一样。

标签: c split dynamic-memory-allocation c-strings function-definition


【解决方案1】:

当您返回tokens 时,它包含从strtok 返回的指针,它们是指向line 的指针。但line此时已不存在。

您分配了内存并使tokens 的各个元素指向该分配的内存。不要用strtok 返回的值覆盖这些值。

【讨论】:

  • line 具体而言超出了范围。在那个时候,它只是一个先前值的幽灵,就像一个闹鬼的字符串一样,它会做各种令人毛骨悚然的事情。
【解决方案2】:

对于初学者来说,函数应该至少像这样声明

char ** split_line( const char *ln );

因为传递的字符串在函数内没有被改变。

如果声明第二个参数来指定分隔符,该函数将更加灵活。

char ** split_line( const char *ln, const char *delim );

其次,使用64256 之类的幻数是个坏主意。如果传递的字符串包含多个63 标记,或者当字符串包含至少一个长度大于63 的标记时,该函数将不起作用。

你在这个循环中动态分配了 64 个数组

for (int i = 0; i < 64; i++)
    tokens[i] = calloc(64, sizeof(char));

并将它们的地址分配给变量tokens 指向的数组元素。但是您立即使用本地数组 line 内的地址重新分配了指针。

tokens[0] = strtok(line, " ");
for (j = 1; (tok = strtok(NULL, " ")) != NULL && j < 63; j++) 
    tokens[j] = tok;

因此该函数会产生大量内存泄漏。并且返回的指针数组会包含无效的指针,因为本地数组line在退出函数后将不再存在。

还有这个说法

tokens[j] = NULL;

是多余的。使用calloc,您最初已经将所有指针设置为NULL

该函数可以如下面的演示程序所示。

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

char ** split_line( const char *s, const char *delim )
{
    size_t n = 0;
    
    for ( const char *p = s; *p; )
    {
        p += strspn( p, delim );
        
        if ( *p )
        {
            ++n;
            p += strcspn( p, delim );
        }
    }
    
    char **tokens = calloc( n + 1, sizeof( char * ) );
    
    if ( tokens )
    {
        size_t i = 0;
        int success = 1;
        
        for ( const char *p = s; success && *p; i += success )
        {
            p += strspn( p, delim );
        
            if ( *p )
            {
                const char *q = p;
                p += strcspn( p, delim );
                
                tokens[i] = malloc( p - q + 1 );
                
                if ( ( success = tokens[i] != NULL ) )
                {
                    memcpy( tokens[i], q, p - q );
                    tokens[i][p-q] = '\0';
                }
            }
        }
        
        if ( !success )
        {
            for ( char **p = tokens; *p; ++p )
            {
                free( *p );
            }
            
            free( tokens );
        }
    }
    
    return tokens;
}

int main(void) 
{
    const char *s = "Each word in this string should be an element in cut_ln.";
    
    char **tokens = split_line( s, " " );
    
    if ( tokens )
    {
        for ( char **p = tokens; *p; ++p )
        {
            puts( *p );
        }
        
        for ( char **p = tokens; *p; ++p )
        {
            free( *p );
        }
    }
    
    free( tokens );

    return 0;
}

程序输出是

Each
word
in
this
string
should
be
an
element
in
cut_ln.

【讨论】:

    猜你喜欢
    • 2018-04-05
    • 2011-02-07
    • 2016-12-28
    • 2016-06-10
    • 2013-04-10
    • 1970-01-01
    • 2021-01-26
    • 1970-01-01
    • 2016-01-27
    相关资源
    最近更新 更多