C言語における文字列連結

C言語で文字列連結を行う。とても簡単に思えるけれど、実はパフォーマンスについて考えることもあるんだよ、というお話。

C言語で2つ文字列の連結して、1つの文字列にするプログラム(関数)を書けるでしょうか? ちょっとC言語でプログラミングを学んだことがあれば簡単ですよね。要求仕様としては2つの引数aとbをとり、どちらもNULターミネートな文字列で、その文字列をヒープから確保した領域で連結して戻り値として返す、という感じの動作です。ヨユーですね。ちょっと書いてみてください。

char* str_join(const char* a, const char* b)
{
    char* p = malloc(strlen(a) + strlen(b) + 1);
    strcpy(p, a);
    strcat(p, b);
    return p;
}

こんな風に書いてしまったあなたは及第点です。個人的には失格です。問題は4行目と5行目で不要なメモリアクセスが発生します。4行目ではstrlen(a)相当の処理が、5行目ではstrlen(a)相当の処理とstrlen(b)相当の処理が発生し、いずれも余計となります。実はstrlen()は速くありません。そのためこの関数は将来的にパフォーマンスボトルネックになる恐れがあるのです。

より良いコードはこうなります。

char* str_join(const char* a, const char* b)
{
    size_t la = strlen(a);
    size_t lb = strlen(b);
    char* p = malloc(la + lb + 1);
    memcpy(p, a, la);
    memcpy(p + la, b, lb + 1);
    return p;
}

strlen()を明示的にまとめることで、余計なメモリアクセスがグっと減ります。ただこのコードも及第点に毛が生えた程度でしょう。エラー処理が無いとかcalloc使ってないとかmemmove使えとかコメント書けとか、ケチはいくらでもつけようがあります。

伝えたかったのは、このような短いコードであっても、メモリのアクセス速度が格段に速いとしても、しっかり意識したプログラムを書かないと簡単に動作速度は遅くなるんだよ、ということです。もちろん1つめの書き方でも動きますしハードウェアが十分に高速な現在では、だいたい問題ないでしょう。ですがこういう見方をできるかどうかというのは、プログラミングの技量を論じる上で重要になってくるものなのです。