C言語で文字列置換

C言語で文字列置換するコードを探すとネット上で見つかるのは以下のような感じ。

どれを見ても置換後文字列の格納エリアを自動でメモリ確保するようなものばかりだけど、システム屋としてコードを書き続けているせいか関数内で malloc することに違和感を感じてしまう。 呼び出し元で責任をもって free すること、といった実装にすると、責任感のないコーダーによっていとも簡単にメモリリークが埋め込まれてしまう。 最初から置換後のエリアを十分に大きく確保して使うこと、といったスタンスの方がわかりやすくてよい。

ということで、単に元の文字列を破壊的に文字列置換するなら以下のような感じでいい。

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

char *replace(char *str, const char *what, const char *with)
{
  if (!*what) { return str; }
  char *what_pos = strstr(str, what);
  if (!what_pos) { return str; }
  const size_t what_len = strlen(what), with_len = strlen(with);
  const char *remain = what_pos + what_len;
  memmove(what_pos + with_len, remain, strlen(remain) + 1);
  memcpy(what_pos, with, with_len);
  return str;
}

char *replace_all(char *str, const char *what, const char *with)
{
  if (!*what) { return str; }
  char *what_pos = str;
  const size_t what_len = strlen(what), with_len = strlen(with);
  while ((what_pos = strstr(what_pos, what)) != NULL) {
    const char *remain = what_pos + what_len;
    memmove(what_pos + with_len, remain, strlen(remain) + 1);
    memcpy(what_pos, with, with_len);
  }
  return str;
}

このコードを応用すれば文字→文字列や文字列→文字といった実装も簡単。

char *replace_chr(char *str, const char what, const char *with)
{
  if (!what) { return str; }
  char *what_pos = strchr(str, what);
  if (!what_pos) { return str; }
  const size_t with_len = strlen(with);
  const char *remain = what_pos + 1;
  memmove(what_pos + with_len, remain, strlen(remain) + 1);
  memcpy(what_pos, with, with_len);
  return str;
}

char *replace_chr_all(char *str, const char what, const char *with)
{
  if (!what) { return str; }
  char *what_pos = str;
  const size_t with_len = strlen(with);
  while ((what_pos = strchr(what_pos, what)) != NULL) {
    const char *remain = what_pos + 1;
    memmove(what_pos + with_len, remain, strlen(remain) + 1);
    memcpy(what_pos, with, with_len);
  }
  return str;
}

この関数なんかは例えばASCIIの制御コードをエスケープ文字にしたい時とかに使える。

char *escape(char *str)
{
  replace_chr_all(str, '\b', "\\b");
  replace_chr_all(str, '\f', "\\f");
  replace_chr_all(str, '\n', "\\n");
  replace_chr_all(str, '\r', "\\r");
  replace_chr_all(str, '\t', "\\t");
  return str;
}