在C语言中,如何动态地重新分配未知数量和大小的字符串表?

在C语言中,如何动态地重新分配未知数量和大小的字符串表?,c,string,memory,resize,C,String,Memory,Resize,更新2:Dave在他的建议中添加了错误检查,我采纳了它,并在单独的回答中添加了一些扩展功能 更新1:问题已解决(不是realloc()问题,而是我没有为空终止分配额外的表槽……我已经纠正了它)。另请参见第一个答案中Dave的建议 原创帖子: 不使用中间数据结构,是否可以在c中动态地重新分配一个完全未知的字符串表 我要做的是生成一个func sarr_make_tokens()来标记任意c字符串,并返回一个c字符串标记表(以NULL结尾) 例如,使用s[]=“你好,残酷的世界!”并调用:char*

更新2:Dave在他的建议中添加了错误检查,我采纳了它,并在单独的回答中添加了一些扩展功能

更新1:问题已解决(不是realloc()问题,而是我没有为空终止分配额外的表槽……我已经纠正了它)。另请参见第一个答案中Dave的建议

原创帖子

不使用中间数据结构,是否可以在c中动态地重新分配一个完全未知的字符串表

我要做的是生成一个func sarr_make_tokens()来标记任意c字符串,并返回一个c字符串标记表(以NULL结尾)

例如,使用s[]=“你好,残酷的世界!”并调用:char**tokens=s_make_tokens(s,“\t!”);我想得到一个以NULL结尾的令牌表

tokens[0] : "hello"
tokens[1] : "cruel"
tokens[2] : "world"
tokens[3] : NULL
下面的代码失败(我猜是在realloc()行中),但我想不出应该传递给它的大小,以便保留任何已存储的令牌

是否有可能,或者我必须首先通过s的本地副本上的strok()循环获取令牌数,然后在令牌表中分配尽可能多的插槽,然后应用另一个strok()循环,以便在表中存储实际令牌

在将令牌复制到令牌表之前,我可以使用一个中间链接列表来存储令牌,但是如果有一种方法可以使用具有正确大小的realloc,那就更好了

我将感谢任何帮助!下面是有问题的代码。。。实际上,它可以工作,但在尝试释放()调用者函数中的已获取令牌表时,seg会出错

#define S_FREE(p) \
    do \
        if ( (p) ) { \
            free( (p) ); \
                (p) = NULL; \
        } \
while (0)

/* --------------------------------------------- */
int sarr_free( char *sarr[] )
{
    register char **cpp = sarr;

    /* sanity check */
    if ( !sarr ) { errno = EFAULT; return 0; }

    while ( *cpp )
        free( *cpp++ );
    free( sarr );

    return 1;           /* TRUE                                   */
}


/* --------------------------------------------- */
char **sarr_make_tokens( char *s, const char *delims )
{
    char **tokens = NULL, **ppchar = NULL;
    size_t toksize = 0;
    register char *cp = NULL;
    register int i=0, j=0;

    /* sanity checks */
    if ( !s || !delims ) { errno = EFAULT; return NULL; }
    if ( !*s || !*delims ) { errno = EINVAL; return NULL; }

    i = 0;
    cp = strtok( s, delims );
    while ( cp != NULL )
    {
            /* add a new slot in the array */
            ppchar = realloc( tokens, (i+1) * sizeof(char *) );
            if ( !ppchar ) {
                    for (j=i-1; j > -1; j--)
                            free( tokens[j] );
                    S_FREE( tokens );
                    errno = ENOMEM;
                    return NULL;
            }
            tokens = ppchar;

            /* make room for the token & copy it into the slot */
            toksize = strlen( cp ) + 1;
            tokens[i] = calloc( toksize, sizeof(char) );
            if ( !tokens[i] ) {
                    for (j=i-1; j > -1; j--)
                            free( tokens[j] );
                    free( tokens );
                    errno = ENOMEM;
                    return NULL;
            }
            memcpy( tokens[i], cp, toksize );

            /* get next token */
            cp = strtok( NULL, delims );

            i++;
    }

    if ( i != 0 ) {         /* while-loop run at least once             */
            ppchar = realloc( tokens, (i+1) * sizeof(char *) );
            if ( !ppchar )
                /* handle error here */
            tokens = ppchar;
            tokens[ i ] = NULL; /* ... NULL terminate the array of tokens   */
    }

    else                /* while-loop did not run at all            */
            errno = ERANGE;     /* ... flag failure of 1st strtok()         */

    return tokens;

}

这很容易。。。使用
strtok()
strdup()
realloc()
,函数相当简单

//EDIT: Now handles errors.
char **tok(char *s, char *delim)
{
    char *str, **arr, **ap;
    int cap=3, fill=0;
    if((str=strdup(s))==NULL) //in case s is read-only.
         goto NoMem;
    if((arr = malloc(cap*sizeof(char*)))==NULL)
         goto NoMem;
    for(s=strtok(str, delim); s; s=strtok(NULL, delim)){
        if(cap<=fill+1)
            if(ap = realloc(arr, (cap=(cap*3)/2)*sizeof(char*)))
                 arr=ap;
            else 
                 goto NoMem;
        if((arr[fill++] = strdup(s))==NULL)
            goto NoMem;
        arr[fill] = NULL;
    }
    free(str);
    return arr;
NoMem:
    if(str) free(str); 
    if(arr){
        for(ap=arr; *ap; ap++)
            free(*ap);
        free(arr);
    }
    return NULL;
}
//编辑:现在处理错误。
char**tok(char*s,char*delim)
{
字符*str,**arr,**ap;
int cap=3,fill=0;
if((str=strdup(s))==NULL)//如果s是只读的。
后藤诺姆;
if((arr=malloc(cap*sizeof(char*)))==NULL)
后藤诺姆;
for(s=strtok(str,delim);s;s=strtok(NULL,delim)){

if(cap问题毕竟不在realloc()中,而是因为在退出函数之前,我没有为NULL终止分配额外的表槽(我已经更正了它,并删除了原始帖子中的相关文本)

Dave还提出了一个更优雅(但不可行)的解决方案:


感谢大家的反馈!

采纳Dave的建议,并对原始函数的功能进行了一点扩展,这样程序员就可以决定s是否应被视为只读(较慢)或不应被视为只读(较快),通过一个extar参数,我得出了以下结论…在错误检查中有点多余,这有点与“更快”的说法相矛盾,但好吧,我还没有仔细考虑…但希望它没有bug

#define S_FREE(p)            \
    do                       \
        if ( (p) ) {         \
            free( (p) );     \
            (p) = NULL;      \
        }                    \
    while (0)

#define SARR_BACKFREE(sarr, ifailed)        \
    do {                                    \
        register int i=0;                   \
        for (i=(ifailed)-1; i > -1; i--)    \
            S_FREE( (sarr)[i] );            \
        S_FREE( (sarr) );                   \
    } while(0)

/* -------------------------------------- */
char *s_strdup( const char *src )
{
    char *s = NULL;
    size_t ssize = 0;

    /* sanity check */
    if ( !src ) { errno = EFAULT; return NULL; }

    ssize = strlen( src ) + 1;
    if ( !(s = malloc(ssize)) ) {
        errno = ENOMEM;
        return NULL;
    }

    return memcpy( s, src, ssize );
}

/* -------------------------------------- */
char **sarr_make_tokens( char *s, char *delims, const int readonly )
{
    char *str = NULL, *cp = NULL;
    int cap = 3, itok = 0;
    char **sarr = NULL, **ppchar = NULL;

    /* sanity checks */
    if ( !s || !delims ) { errno = EFAULT; return NULL; }
    if ( !*s || !*delims ) { errno = EINVAL; return NULL; }

    /* treat s as a string literal? */
    if ( readonly ) {
        if ( (str = s_strdup( s )) == NULL )
            goto ret_nomem;
    }
    else    str = s;

    /* allocate table of strings sarr */
    if ( (sarr = malloc( cap * sizeof(char *) )) == NULL )
        goto ret_nomem;

    /* tokenize str and store tokens in sarr */
    for ( cp=strtok(str, delims); cp; cp=strtok(NULL, delims) )
    {
        if( cap <= itok+1 )
        {
            ppchar = realloc(sarr, (cap=(cap*3)/2) * sizeof(char *) );
            if ( !ppchar )
                goto clean_and_ret_nomem;
            sarr = ppchar;
        }

        if ( (sarr[ itok ] = s_strdup( cp )) == NULL )
            goto clean_and_ret_nomem;

        itok++;
    }

    /* NULL terminate sarr */
    sarr[ itok ] = NULL;

    if ( readonly )
        S_FREE( str );
    return sarr;

clean_and_ret_nomem:
    SARR_BACKFREE(sarr, itok);
ret_nomem:
    if ( readonly )
        S_FREE( str );          /* S_FREE() works ONLY if str != NULL */
    errno = ENOMEM;
    return NULL;
}
#定义S#U自由(p)\
做\
如果((p)){\
自由((p))\
(p) =零\
}                    \
而(0)
#定义SARR_回流阀(SARR,如图所示)\
做{\
寄存器int i=0\
对于(i=(ifailed)-1;i>-1;i--)\
S_FREE((sarr)[i])\
无硫((sarr))\
}而(0)
/* -------------------------------------- */
char*s_strdup(const char*src)
{
char*s=NULL;
大小\u t ssize=0;
/*健康检查*/
如果(!src){errno=EFAULT;返回NULL;}
ssize=strlen(src)+1;
如果(!(s=malloc(ssize))){
errno=ENOMEM;
返回NULL;
}
返回memcpy(s、src、ssize);
}
/* -------------------------------------- */
char**sarr\u make\u标记(char*s、char*delims、const int只读)
{
char*str=NULL,*cp=NULL;
int cap=3,itok=0;
字符**sarr=NULL,**ppchar=NULL;
/*健康检查*/
如果(!s | |!delims){errno=EFAULT;返回NULL;}
if(!*s | |!*delims){errno=EINVAL;返回NULL;}
/*是否将s视为字符串文字*/
如果(只读){
如果((str=s_strdup(s))==NULL)
去重新命名;
}
else-str=s;
/*分配字符串表*/
if((sarr=malloc(cap*sizeof(char*)))==NULL)
去重新命名;
/*标记化str并将标记存储在sarr中*/
for(cp=strtok(str,delims);cp;cp=strtok(NULL,delims))
{

如果(cap),你应该将你的解决方案作为答案发布,过一段时间后,将其标记为已接受,而不是将已解决的问题添加到标题中。这不是StackOverflow的“标准”。哦,我明白了,我现在就开始了,谢谢!现在还不能这样做,因为。。。“哦!您的答案无法提交,因为:*声誉低于100的用户在提问后8小时内无法回答自己的问题。您可以在4小时内自行回答。在此之前,请使用评论或编辑您的问题。”这看起来很酷,谢谢!但是,strdup()不是标准的ANSI C func(有很多免费的(str)我想也不见了。)你觉得我忘了把
free()
放在哪里了?我指的是你帖子的原始版本:)顺便说一句,我刚刚用一个自定义的strdup(const char src){char*s=NULL;size\u t ssize=0;/sanity check*/if(!src){errno=EFAULT;return NULL;}ssize=strlen(src)+1;if(!(s=malloc(ssize)){errno=ENOMEM;return NULL;}return memcpy(s,src,ssize);}它似乎在循环的最后一行进入了一个无限循环:arr[fill++]=strdup(s);…它正确地迭代了一次,然后进入无限循环…Win7Oh上的mingw gcc和pelles-c都是一样的,您已经将它从strdup(str)更改为要升级…那么,让我再检查一次。现在它很好,很好!添加