ポインタと文字列
ポインタの活用例その2
文字列はchar型配列に保存する、というのは文字型と文字列の項で説明しました。
そして、ポインタと配列は似ている、というのもポインタと配列で説明しました。
ポインタを利用すれば、文字列はもう少し便利に扱うことができるようになります。
#include <stdio.h>
int main()
{
char str[] = "ABCD";
//char str[] = { 'A', 'B', 'C', 'D', '\0' };
char *strP = "EFGH";
printf("%s\n", str); //ABCD
printf("%s\n", strP); //EFGH
getchar();
}
5行目は今まで通りのchar型文字列です。
まずはこの処理を、メモリ上の処理に着目して詳しく追っていきます。
文字列リテラルは、プログラムの実行開始から終了まで常にメモリ上に存在する、という特徴があります。
配列の初期化子に文字列リテラルを指定すると、その文字列の長さ分(プラスNULL文字)のサイズを持つ配列が自動的に生成され、そこに一文字ずつ値がコピーされます。
6行目のコメントに書いた通り、文字列リテラルでの初期化は、文字列を一文字ずつの文字に分解した初期化リストを指定するのと同等です。
次に、8行目の処理を見てみます。
char *strP = "EFGH";
文字列リテラルを評価すると、その文字列リテラルの先頭のアドレス(char型のポインタ)が返ってきます。
これをchar型のポインタ変数で受け取っています。
配列の変数もポインタ変数も、「そのまま」書いた場合にはデータの先頭を示すポインタを返す、という点は共通しています。
そのため、printf
関数の引数指定では全く同じ書き方ができるのです。
(10、11行目)
文字列の終端はどちらもNULL文字が目印です。
ちなみに、ポインタ変数も変数ですから、アドレスを保存しておく領域がメモリ上に確保されます。
この時のサイズは32bit環境ならば4バイトなので、上記のイメージ図では4マス消費しています。
(64bit環境では8バイト)
文字列のポインタのメリット
文字列のポインタが配列と同じように扱えたとしても、ただそれだけではメリットがありません。
配列にはない便利な点として、代入だけでポインタ変数の文字列を別の文字列に差し替えることができます。
char str[] = "ABCD";
char *strP1 = "EFGH";
char *strP2;
//これはNG
str = "IJKL";
//これはOK
strP1 = "IJKLMNOPQRSTU";
//これもOK
strP2 = strP1;
6行目のような書き方はできないのは文字型と文字列の項で説明した通りです。
文字列配列に別の文字列を代入したい場合は、一文字ずつ書き換えていくか、文字列操作関数(strcpy関数など)を使用する必要があります。
しかしポインタ変数ならばこれが可能です。
文字列リテラルを評価すると、その文字列へのポインタが返ってきます。
9行目の処理はそれをポインタ変数に格納しているだけですから、問題なく動くコードとなります。
もちろんstrP1
が示す文字列も置き換わります。
文字列の長さに制限がないというのも大きな利点です。
9行目は最初に代入した文字列よりも長い文字列を再代入しています。
ポインタ変数の格納に必要なメモリ容量はどれだけ長い文字列であっても4バイトです。
(32bitのとき)
文字列リテラルはプログラムの実行開始時にはすでにメモリ上に存在しているのですから、その場所をポインタに代入するだけで別の文字列に差し替えることができます。
配列のときのように文字列を保存するために配列サイズをあらかじめ確保しておく必要もありません。
(文字列の終端はNULL文字で判断できます)
さらに、12行目のように別のポインタ変数の代入も可能です。
これはstrP2
をstrP1
と同じ文字列を指すように指定しています。
配列の時はこういう書き方はできませんでしたが、非常にシンプルに記述できます。
文字列のポインタのデメリット
文字列配列よりも利点が多い文字列のポインタですが、文字列配列ではできて文字列のポインタではできないこともあります。
それは、C言語では文字列リテラルは書き換えてはならないというルールがあるためです。
char str[] = "ABCD";
char *strP = "EFGH";
//これはOK
str[1] = 'Z';
//これはNG
strP[1] = 'Z';
//こうやってもNG
*(strP + 1) = 'Z';
文字列配列のときは、5行目のように文字列を書き換えることができました。
配列は、文字列リテラルとは別の場所に容量を確保し、そこに値を保存していますから、書き換えても問題ありません。
しかし、ポインタが指し示すのは文字列リテラル自身です。
文字列リテラルは書き換えてはならないというルールがあるため、8行目や11行目のような記述はできません。
strcpy
等の文字列操作関数を用いても書き換えはできません。
実際にこのコードをコンパイルすると、問題なく動くこともあります。
しかしそれはたまたま動いただけの話で、常に問題がないとは限りません。
文字列リテラルを書き換えた時の動作は未定義なので、どのような動作を引き起こすかわからないのです。
そのため、一部を書き換える可能性がある文字列はchar型配列、書き換える可能性がない文字列はポインタ、という使い分けが良いと思います。
文字列ポインタは書き換えはできませんが別の文字列リテラルを指すように変更することは容易なので、多くの場合はポインタを用いることになるでしょう。
const char*型
文字列リテラルは書き換え不可であるため、そのアドレス保存するポインタ変数はconstにしておくことをお勧めします。
char* a = "abc";
const char* b = "def";
a[0] = 'z'; //未定義動作
//b[0] = 'z'; //コンパイルエラー
b = a; //文字列の差し替えは可能
const char* const c = "ghi";
//c[0] = 'z'; //コンパイルエラー
//c = a; //コンパイルエラー
変数a
はただのchar*型で、値の書き換えが文法上は可能です。
しかしそれは文字列リテラルの書き換えとなってしまうので、未定義動作を引き起こします。
変数b
はconst char*型で、これは値の書き換えはコンパイル時にエラーを検出してくれます。
書き換えを禁止するのはポインタの先の値ですから、ポインタ自体の書き換え(文字列の差し替え)は問題なく可能です。
もしそれさえも禁止したい場合は変数c
のように「const char* const」型が使用できます。
C言語では文字列リテラルはchar*型ですが、C++言語ではconst char*型です。
また、C++では型のチェックが厳密になっていて、const char*型をchar*型変数に格納しようとすると型が一致せずコンパイルエラーになります。