こんにちは、ナナです。
C言語は文字を扱うことが苦手な言語です。そのため、文字情報の制御がうまくできるかは、プログラマーである皆さんの腕に掛かっています。
本記事では、2つの文字列を連結させたいというニーズに応える「strcat関数」の使い方と、使う上での注意点を解説していきます。
本記事では次の疑問点を解消する内容となっています。
それでは、「strcat関数」による文字列の連結方法について学んでいきましょう。
文字列を連結する標準ライブラリ関数:strcat関数
C言語には2つの文字列を連結するための標準ライブラリ関数が用意されています。
#include <string.h>
char * strcat(char * dest, const char * src);
「strcat」とは「string(文字列)」を「 concatenate(連結する)」を省略した名前となっています。
strcat関数の仕様
引数で指定された文字列同士を連結するための関数です。
includeファイル | string.h |
関数仕様 | char * strcat(char * dest, const char * src); |
第1引数 | dest:結合先の文字列へのポインタ |
第2引数 | src:第1引数の終端に結合したい文字列へのポインタ |
戻り値 | 第1引数のポインタが返却される |
特記事項 | 文字列は第1引数の終端に結合されることに注意が必要。 |
strcat関数は使い方を誤るとメモリ破壊や例外を起こしてしまうため、扱い方をしっかりと把握してから使いましょう。
それでは使い方を解説していきますよ!
strcat関数を使った文字列結合のサンプルコード
strcat関数を使った簡単なサンプルコードは次のものです。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char hello[16] = "Hello"; // 結合先
char world[] = "World"; // 結合文字列
strcat(hello, world);
printf("%s", hello);
return 0;
}
HelloWorld
第1引数の文字列「Hello」に第2引数の「World」が結合され「HelloWorld」の文字列が作成できました。
第1引数の文字列の後ろに第2引数の文字列が結合されること、を理解しましょう。
_CRT_SECURE_NO_WARNINGSとは
ヘッダファイルのインクルードの前に「#define _CRT_SECURE_NO_WARNINGS」のマクロ定義が存在してます。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
これはVisual Studio環境においてstrcat関数を使用するために必要な定義となっています。実はstrcat関数は危険性を伴う関数のため、使用が推奨されていません。
より安全な「strcat_s関数」を利用する場合は、次のようにマクロ定義は不要となります。
#include <stdio.h>
#include <string.h>
int main(void)
{
char hello[32] = "Hello"; // 結合先
char world[] = "World"; // 結合文字列
strcat_s(hello, 32, world);
printf("%s", hello);
return 0;
}
本問題に関しては「strcpy関数」においても発生するため、詳しく知りたい方は『strcpy_sへの移行【strcpyより安全なコピー方式】』の記事を読んでおくとよいでしょう。
「strcat_s関数」は、第2引数に第1引数の配列要素数を指定しますよ!
strcat関数で文字列結合をするときの2つの注意点
strcat関数を利用する際に注意すべきことが2つあります。この知識は必ず頭に入れておきましょう。
注意点①:文字列メモリ領域のバッファオーバーラン
C言語においてメモリとは明示的な予約が必要です。予約した範囲外に対してのメモリアクセスは決してしてはなりません。
文字列を連結するということは、文字列が長くなるということです。次のプログラムはhello配列の要素を6個分しか予約していません。
このhello配列へ文字列を連結すると、いったいどうなってしまうのでしょうか?
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char hello[6] = "Hello"; // 結合先
char world[6] = "World"; // 結合文字列
strcat(hello, world);
printf("%s", hello);
return 0;
}
HelloWorld
結果として「HelloWorld」が表示され、正しく動いているように見えます。しかし、重大な問題が発生しています。
このプログラムは最後まで実行すると「Run-Time Check Failure #2 – Stack around the variable ‘hello’ was corrupted.」という例外が発生します。
ローカル変数の場合は「スタック破壊」、グローバル変数の場合は近隣のメモリ内容が破壊されます。
結合先となる第1引数のメモリは、結合後の文字列の長さを設計的に見積もった上であらかじめ確保しておく必要があります。
char hello[32] = "Hello"; // 結合先
char world[] = "World"; // 結合文字列
メモリ破壊はシステムに致命的な問題を起こします。文字列制御は簡単にメモリ破壊を起こしてしまうため、皆さん本当に注意しましょう!
注意点②:書き込み禁止メモリへの文字列連結
C言語では文字列の定義方法によって情報の配置場所が変わります。
連結時に注意が必要なのが「書き込み禁止エリア」に配置された文字列への連結処理です。
次のプログラムはstrcat関数呼び出し時に例外が発生します。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char world[] = "World"; // 結合文字列
// 文字列リテラルへの連結
strcat("Hello", world);
return 0;
}
次のプログラムも同様の問題の理由により書いてはいけません。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
int main(void)
{
char * hello = "Hello"; // 結合先
char world[] = "World"; // 結合文字列
// ポインタからの文字列リテラルへの連結
strcat(hello, world);
return 0;
}
これらの「Hello」文字列は書き込み禁止メモリへと配置されます。
そのため、連結処理でメモリへ書き込もうとした瞬間、例外が発生します。
文字列の書き方によって、文字列の情報がメモリのどこに配置されるかを理解しておく必要があります。
strcat関数による文字列連結のまとめ
- 文字列を連結したい時はstrcat関数を使え
- 可能ならばより安全なstrcat_s関数を使え
- 連結先となる第1引数のメモリは結合後のサイズも踏まえた上で確保せよ
- 書き込み禁止メモリに対する連結はやってはいけない!
文字列操作はくれぐれも慎重にね!