如何在C语言中替换子字符串?

3

这个例子能够工作,但我认为它存在内存泄漏问题。这个函数被用在简单的Web服务器模块中,因此如果你使用这个函数,共享内存会不断增长。

    char *str_replace ( const char *string, const char *substr, const char *replacement ){
      char *tok = NULL;
      char *newstr = NULL;
      char *oldstr = NULL;
      if ( substr == NULL || replacement == NULL ) return strdup (string);
      newstr = strdup (string);
      while ( (tok = strstr ( newstr, substr ))){
        oldstr = newstr;
        newstr = malloc ( strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) + 1 );
        memset(newstr,0,strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) + 1);
        if ( newstr == NULL ){
          free (oldstr);
          return NULL;
        }
        memcpy ( newstr, oldstr, tok - oldstr );
        memcpy ( newstr + (tok - oldstr), replacement, strlen ( replacement ) );
        memcpy ( newstr + (tok - oldstr) + strlen( replacement ), tok + strlen ( substr ), strlen ( oldstr ) - strlen ( substr ) - ( tok - oldstr ) );
        memset ( newstr + strlen ( oldstr ) - strlen ( substr ) + strlen ( replacement ) , 0, 1 );
        free (oldstr);
      }
      return newstr;
    }

4个回答

11

一个问题是如果替换字符串包含搜索字符串,你会一直循环下去(直到内存耗尽)。

例如:

char *result = str_replace("abc", "a", "aa");

此外,每次替换一个实例时进行另一个malloc/free非常昂贵。

更好的方法是对输入字符串进行恰好两次遍历:

  • 第一次遍历,计算出搜索字符串出现的次数

  • 现在你知道了有多少匹配项,计算出结果的长度并只进行一次malloc :

    strlen(string) + matches*(strlen(replacement)-strlen(substr)) + 1

  • 第二次遍历源字符串,进行复制/替换操作


1

解释一下这部分:

if ( substr == NULL || replacement == NULL ) return strdup (string);

为什么要返回现有字符串的副本?这会导致内存泄漏,而且是不必要的。

如果 while 循环被跳过(即条件从未满足),则您也永远不会释放该副本。


公平起见,现有的字符串总是被复制并返回一个新的字符串 - 不管是在有替换的情况下(return newstr;)还是没有替换的情况下(return strdup(string);)。在这两种情况下,释放原始输入的 string(不是很好的参数名称)可以减少内存使用,假设原始脚本不再需要它(或者替代地,在函数中替换原始的 string 并返回 void)。 - Rudu
1
说实话,作为一名程序员,如果我将const char *传递给函数并且它为我释放了它,我会感到有些被愚弄了 :) - EboMike

1

这将在“src”中替换所有出现的“str”为“rep”...

void strreplace(char *src, char *str, char *rep)
{
    char *p = strstr(src, str);
    do  
    {   
        if(p)
        {
            char buf[1024];
            memset(buf,'\0',strlen(buf));

            if(src == p)
            {
                strcpy(buf,rep);
                strcat(buf,p+strlen(str));  
            }
            else
            {
                strncpy(buf,src,strlen(src) - strlen(p));
                strcat(buf,rep);
                strcat(buf,p+strlen(str));
            }

            memset(src,'\0',strlen(src));
            strcpy(src,buf);
        }   

    }while(p && (p = strstr(src, str)));
}

0
  • strdup不符合C89/C99标准,因此您的代码 => 不是ANSI C
  • 最好在malloc之后直接进行NULL测试

这里有一个示例,只使用了一个新的内存块:

/* precondition: s!=0, old!=0, new!=0 */
char *str_replace(const char *s, const char *old, const char *new)
{
  size_t slen = strlen(s)+1;
  char *cout = malloc(slen), *p=cout;
  if( !p )
    return 0;
  while( *s )
    if( !strncmp(s, old, strlen(old)) )
    {
      p  -= cout;
      cout= realloc(cout, slen += strlen(new)-strlen(old) );
      p  += strlen( strcpy(p=cout+(int)p, new) );
      s  += strlen(old);
    }
    else
     *p++=*s++;

  *p=0;
  return cout;
}

你可能想要缓存 strlen(old) 的值,这样在每次迭代时计算会更快。 - jbernadas
1
p += strlen( strcpy(p=cout+(int)p, new) ); 这一行会引起未定义的行为 - 在没有中间序列点的情况下对“p”进行了两次赋值。虽然写得很难懂,但还是挺巧妙的。 - David Gelhar

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