文字列のコピー

文字列操作2

文字列配列を別のchar型配列にコピーしたい場合、そのまま代入演算子(=)でコピーすることはできません。


char str1[10], str2[10];

//こういうことはできない
str1 = str2;

文字列をコピーするにはstrcpy関数、strncpy関数、またはstrcpy_s関数、strncpy_s関数を使用します。
これらは微妙に動作が異なるので注意が必要です。

strcpy_s関数、strncpy_s関数はコンパイラによっては使用できません。

strcpy関数、strncpy関数はVisualStudio既定の設定では使用するとエラーとなります。
詳しくはページ後段の_s系関数とエラー表示についてを参照してください。

strcpy関数、strncpy関数


#include <stdio.h>
#include <string.h>

int main()
{
	const char strSource[] = "ABCDE"; //コピー元
	char strDest1[32];	//コピー先1
	char strDest2[32];	//コピー先2

	strcpy(strDest1, strSource);
	strncpy(strDest2, strSource, sizeof(strDest2));

	printf("%s\n", strDest1);
	printf("%s\n", strDest2);
	getchar();
}
ABCDE
ABCDE
char *strcpy(
 char *strDestination,
 const char *strSource
);
文字列strDestinationに文字列strSourceをコピーする。
戻り値は文字列strDestination。
char *strncpy(
 char *strDest,
 const char *strSource,
 size_t count
);
文字列strDestに文字列strSourceをcount文字分コピーする。
戻り値は文字列strDest。

strcpy関数はコピー元文字列の先頭からNULL終端(NULL文字)が表われるまでの文字列を、NULL終端を含めてコピー先文字列にコピーします。
配列サイズのチェック機能はないので、コピー先配列のサイズが足りない場合に配列の範囲外にまで書き込んでしまうという致命的なエラーが発生するおそれがあります。
(バッファオーバーランバッファオーバーフローという)

strncpy関数も基本的には同じ動作ですが、コピーする最大文字数を第三引数countで制限できます。
ここにコピー先配列のサイズを指定すればstrncpy関数実行時のバッファオーバーランは防げます。

しかしコピー先の配列のサイズが足りない場合、コピー結果はNULL終端を含まない不正な文字列となります。
つまりその不正な文字列の使用時にバッファオーバーランが発生します。
C言語においてNULL終端のない文字配列は文字列ではなく単なる「文字の配列」なので、文字列として使用してはなりません。

strncpy関数の実行後にコピー先配列の終端にNULL文字を挿入することでバッファオーバーランを避けることができます。
ただしサイズが足りない以上は文字列を途中で切り捨てるしかないので、文字列が壊れる可能性はあります。


const char strSource[] = "ABCDE";
char strDest[3];

//NULL終端を含まない文字列となる
strncpy(strDest, strSource, sizeof(strDest));

//バッファオーバーラン
//printf("%s\n", strDest);

//配列の終端にNULL文字を挿入
strDest[sizeof(strDest) - 1] = '\0';

//安全
//文字列は切り捨てられる
printf("%s\n", strDest);
AB

strcpy_s関数、strncpy_s関数


#include <stdio.h>
#include <string.h>

int main()
{
	const char strSource[] = "ABCDE"; //コピー元
	char strDest1[32];	//コピー先1
	char strDest2[32];	//コピー先2

	strcpy_s(strDest1, sizeof(strDest1), strSource);
	strncpy_s(strDest2, sizeof(strDest2), strSource, sizeof(strDest2) - 1);
	//↑と↓は同じ意味
	//strncpy_s(strDest2, sizeof(strDest2), strSource, _TRUNCATE);

	printf("%s\n", strDest1);
	printf("%s\n", strDest2);
	getchar();
}
ABCDE
ABCDE
errno_t strcpy_s(
 char *dest,
 rsize_t dest_size,
 const char *src
);
サイズdest_sizeの文字列destに文字列srcをコピーする。
正常終了すると0を、エラーの場合は0以外を返す。
errno_t strncpy_s(
 char *strDest,
 size_t numberOfElements,
 const char *strSource,
 size_t count
);
サイズnumberOfElementsの文字列strDestに文字列strSourceをcount文字分コピーする。
正常終了すると0を、エラーの場合は0以外を返す。
文字列strDestにNULL文字を含めることができなかった場合はプログラムが停止する。

strcpy_s関数はstrncpy関数と動作が似ています。
引数は順序が異なるだけでなく意味も異なります。
最大の違いはコピー先配列のサイズが足りない場合はエラーとなりプログラムが停止する点です。
(正確には、第二引数dest_sizeの値が第三引数srcのサイズ未満の場合)
strncpy関数ではサイズが足りなくても文字を切り捨てることでコピー可能でしたが、strcpy_s関数は不正な文字列の生成は許されていないというわけです。

プログラムが停止するというのは不便に思えるかもしれませんが、バッファオーバーランが起こるよりはプログラムが停止してくれた方が安全性が高いプログラムとなります。

strncpy_s関数はさらに引数が増えており、実際にコピーする文字数を制限できます。
第二引数のnumberOfElementsはコピー先の配列のサイズを知らせるために使用され、第四引数のcountは実際にコピーするサイズの決定のために使用されます。

strcpy_s関数は文字列の切り捨ては不正な文字列となるため許可されていませんでしたが、strncpy_s関数の第四引数countで文字列数を制限した場合、countの位置にNULL文字が自動で挿入されます。


char strDest[30];

strncpy_s(strDest, sizeof(strDest), "ABCDE", 3);

//↑は↓のような動作イメージ

strDest[0] = 'A';
strDest[1] = 'B';
strDest[2] = 'C';

//count位置にNULL文字挿入
strDest[3] = '\0';

コピー元文字列のNULL文字ごとコピーされた場合はcount位置へのNULL文字の挿入は行いません。


char strDest[30];

strncpy_s(strDest, sizeof(strDest), "ABC", 5);

//↑は↓のような動作イメージ

strDest[0] = 'A';
strDest[1] = 'B';
strDest[2] = 'C';
strDest[3] = '\0';//これは元の文字列のNULL文字

//この動作はしない
//strDest[5] = '\0';

つまり「コピー先配列のサイズ-1」を指定すれば配列サイズに関わらず安全な文字列のコピーが行えます。


#include <stdio.h>
#include <string.h>

int main()
{
	const char strSource[] = "ABCDE";
	char strDest1[3];
	char strDest2[32];

	strncpy_s(strDest1, sizeof(strDest1), strSource, sizeof(strDest1) - 1);
	strncpy_s(strDest2, sizeof(strDest2), strSource, sizeof(strDest2) - 1);

	printf("%s\n", strDest1);
	printf("%s\n", strDest2);
	getchar();
}
AB
ABCDE

なお_TRUNCATEという定数を指定すると「コピー先配列のサイズ-1」を指定した場合と同じ動作になります。


strncpy_s(strDest, sizeof(strDest), strSource, sizeof(strDest) - 1);
//↑↓同じ意味
strncpy_s(strDest, sizeof(strDest), strSource, _TRUNCATE);

_s系関数とエラー表示について

関数名の末尾に「_s」が付くものは、通常版(「_s」が付ついていない版)よりもセキュアな作りになっており、使用が推奨されています。
(セキュア=より安全性が高いという意味)
通常版の関数の多くはVisualStudioで使用するとエラーになります。
strcpy関数、strncpy関数もそのひとつです。

どの関数も使い方さえ間違えなければ問題は起こりませんが、人はミスをするものですし、できればセキュア版を使用するクセを付けたほうが良いでしょう。

逆に言えばセキュア関数も使い方を誤れば危険な場合もあります。

どうしても通常版を使用したい場合はオプションを変更すれば使用できるようになります。

非セキュア関数を使用できるようにする方法その1 非セキュア関数を使用できるようにする方法その2

「プロジェクト」メニューから「○○のプロパティ」を開きます。
「○○」は現在のプロジェクト名です。

「構成プロパティ」→「C/C++」→「全般」を開き、ウィンドウ右側の「SDLチェック」を「いいえ」に変更します。