【问题标题】:Inserting strings into another string in C在C中将字符串插入另一个字符串
【发布时间】:2017-10-26 15:20:34
【问题描述】:

我正在实现一个函数,给定一个字符串、一个字符和另一个字符串(因为现在我们可以称它为“子字符串”);将子字符串放在字符串中字符所在的任何位置。 为了更好地解释我,给定这些参数,这是函数应该返回的(伪代码):

func ("aeiou", 'i', "hello")  ->  aehelloou

我正在使用来自string.h lib 的一些函数。我已经测试了它,结果非常好:

char *somestring= "this$ is a tes$t wawawa$wa";
printf("%s", strcinsert(somestring, '$', "WHAT?!") );

Outputs:    thisWHAT?! is a tesWHAT?!t wawawaWHAT?!wa

所以现在一切都很好。问题是当我尝试对例如这个字符串做同样的事情时:

char *somestring= "this \"is a test\" wawawawa";
printf("%s", strcinsert(somestring, '"', "\\\"") );

因为我想将每个 " 更改为 \" 。当我这样做时,PC 崩溃了。我不知道为什么,但它停止工作然后关闭。我已经了解了string.h lib 的某些功能的不良行为,但我找不到任何相关信息,非常感谢任何帮助。

我的代码:

#define salloc(size) (str)malloc(size+1) //i'm lazy
typedef char* str;

str strcinsert (str string, char flag, str substring)
{
    int nflag= 0; //this is the number of times the character appears
    for (int i= 0; i<strlen(string); i++)
        if (string[i]==flag)
            nflag++;
    str new=string;
    int pos;
    while (strchr(string, flag)) //since when its not found returns NULL
    {
        new= salloc(strlen(string)+nflag*strlen(substring)-nflag);
        pos= strlen(string)-strlen(strchr(string, flag));
        strncpy(new, string, pos);
        strcat(new, substring);
        strcat(new, string+pos+1);
        string= new;      
    }
    return new;
}

感谢您的帮助!

【问题讨论】:

  • typedef char* str;
  • 那个宏——也很讨厌。
  • 顺便说一句,使用 new 作为标识符肯定会使这段代码对 C++ 无效,因此转换 malloc() 的结果充其量是多余的。
  • 该死的@chux 是真的
  • 您还应该知道,与 Python 不同,strlen 实际上并不是免费的。您应该缓存结果,而不是一遍又一遍地调用它。

标签: c string malloc ansi-c


【解决方案1】:

一些建议:

  • 避免typedef char* str;char * 类型在 C 中很常见,屏蔽它只会让你的代码更难被审查
  • 出于完全相同的原因避免使用#define salloc(size) (str)malloc(size+1)。另外不要在 C 中转换 malloc
  • 每次你写一个malloc(或callocrealloc)都应该有一个对应的free:C没有垃圾回收
  • 动态分配成本高,仅在需要时使用。换句话说,循环内的malloc 应该被查看两次(特别是如果没有对应的free
  • 总是测试分配函数(不相关:和 io),当您耗尽内存时,malloc 将简单地返回 NULL。一个好的错误消息比崩溃更容易理解
  • 学习使用调试器:如果您在调试器下执行代码,错误会很明显

下一个原因:如果替换字符串包含原来的字符串,你会再次陷入它并在无限循环中运行

一种可能的解决方法:在循环之前分配结果字符串,并在原始字符串和结果中都前进。它将使您免于不必要的分配和取消分配,并且不受替换字符串中存在的原始字符的影响。

可能的代码:

// the result is an allocated string that must be freed by caller
str strcinsert(str string, char flag, str substring)
{
    int nflag = 0; //this is the number of times the character appears
    for (int i = 0; i<strlen(string); i++)
        if (string[i] == flag)
            nflag++;
    str new_ = string;
    int pos;
    new_ = salloc(strlen(string) + nflag*strlen(substring) - nflag);
    // should test new_ != NULL
    char * cur = new_;
    char *old = string;
    while (NULL != (string = strchr(string, flag))) //since when its not found returns NULL
    {
        pos = string - old;
        strncpy(cur, old, pos);
        cur[pos] = '\0';             // strncpy does not null terminate the dest. string
        strcat(cur, substring);
        strcat(cur, string + 1);
        cur += strlen(substring) + pos; // advance the result
        old = ++string;                 // and the input string
    }
    return new_;
}

注意:我没有还原 strsalloc 但你确实应该这样做。

【讨论】:

  • strlen(string) 是一个循环是computational expensive,编译器不理解/优化这个调用。然而这段代码确实保留了 OP 的风格
  • strchr(string, flag) 的角落问题是flag == 0while 循环将迭代并找到空字符,然后使用 old = ++string;,下一个 strchr(string, ... 将是 UB。
【解决方案2】:

在第二个循环中,您总是在字符串中查找第一个 flag 字符。在这种情况下,这将是您刚刚从 substring 插入的那个。 strchr 函数将始终找到该引用并且永远不会返回 NULL,因此您的循环将永远不会终止并只会继续分配内存(而且还不够,因为您的字符串会任意增长)。

说到分配内存,你需要更加小心。与 Python 不同,C 不会自动通知您何时不再使用内存。任何你malloc必须freed。您还分配了比您需要的更多的内存:即使在您的工作 "this$ is a tes$t wawawa$wa" 示例中,您也为循环的每次迭代 上的完整字符串分配了足够的空间,而不是 free 中的任何一个。您应该只在第二个循环之前运行一次分配。

这并不像上面的东西那么重要,但你也应该注意性能。对strcatstrlen 的每次调用都会遍历整个字符串,这意味着您查看它的次数远远超过您的需要。您应该保存strlen 的结果,并将新字符串直接复制到您知道NUL 终止符所在的位置。 strchr 也是如此;您已经替换了字符串的开头,不想再浪费时间查看它,除了导致当前错误的部分。

与这些问题相比,你的typedef和macro的cmets中提到的样式问题相对较小,但仍然值得一提。 C 中的 char* 与 Python 中的 str 不同;尝试将其typedef 使用相同的名称只会使您更有可能尝试将它们视为相同并遇到这些问题。

【讨论】:

    【解决方案3】:

    我不知道为什么,但它停止工作

    strchr(string, flag) 正在查看整个字符串中的标志。搜索需要限制在字符串中尚未检查/更新的部分。通过重新搜索部分替换的字符串,代码会一遍又一遍地找到flag


    整个字符串管理方法需要重新设计。由于 OP 报告了 Python 背景,我发布了一个非常 C 的方法,因为在这里模仿 Python 不是一个好方法。 C 是不同的,尤其是在内存管理方面。


    未经测试的代码

    // Look for needles in a haystack and replace them
    // Note that replacement may be "" and result in a shorter string than haystack
    char *strcinsert_alloc(const char *haystack, char needle, const char *replacment) {
      size_t n = 0;
      const char *s = haystack;
      while (*s) {
        if (*s == needle) n++;  // Find needle count
        s++;
      }
      size_t replacemnet_len = strlen(replacment);
      //                        string length  - needles + replacements      + \0
      size_t new_size = (size_t)(s - haystack) - n*1     + n*replacemnet_len + 1;
      char *dest = malloc(new_size);
      if (dest) {
        char *d = dest;
        s = haystack;
        while (*s) {
          if (*s == needle) {
            memcpy(d, s, replacemnet_len);
            d += replacemnet_len;
          } else {
            *d = *s;
            d++;
          }
          s++;
        }
        *d = '\0';
      }
      return dest;
    }
    

    【讨论】:

      【解决方案4】:

      在您的程序中,您面临输入问题 -

      char *somestring= "this \"is a test\" wawawawa";
      

      因为您想将 " 替换为 \"

      第一个问题是每当您在string 中将\" 替换为\" 时,在下一次迭代中strchr(string, flag) 将找到\" 中最后插入的"。因此,在随后的交互中,您的字符串将像这样形成 -

      this \"is a test" wawawawa
      this \\"is a test" wawawawa
      this \\\"is a test" wawawawa
      

      因此,对于输入字符串 "this \"is a test\" wawawawa",您的 while 循环将运行无限次,因为每次 strchr(string, flag) 找到最后插入的 "\"

      第二个问题是您在每次迭代中在while 循环中进行的内存分配。分配给new 的内存没有free()。所以当while循环无限运行时,它会吃掉所有的内存,这会导致-the PC collapses

      要解决这个问题,在每次迭代中,您应该只在从最后插入的substring 之后的字符开始到字符串末尾的字符串中搜索flag。另外,请确保free() 动态分配的内存。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-02-11
        • 1970-01-01
        • 2016-02-07
        • 1970-01-01
        • 2012-09-25
        • 2022-01-08
        • 2015-04-04
        相关资源
        最近更新 更多