![見出し画像](https://assets.st-note.com/production/uploads/images/111503680/rectangle_large_type_2_5d387fb9f8673345a6bc95909021bb71.png?width=1200)
C言語のポインタと文字列
C言語を始めて、まず最初に混乱するのがポインタでしょう。
〔ここでは、C言語として話しますが、C++言語でも通じる内容です〕
C言語は、文字列や配列をひと固まりのように扱えますが、
それはポインタを利用した、巧みな方法をであるため、混乱も起こります。
char text[10];と書くと、文字が格納できる10個の箱が確保されます。
正確には、textと名前のついた、char型の、サイズが10の、配列が確保されます。
これに、
text[0] = 'A';
text[1] = 'B';
text[2] = 'C';
text[3] = '\0';
とすると「ABC」という文字が格納された文字列となります。
![](https://assets.st-note.com/img/1690102433084-l9BsFR3k3k.png)
「'\0'」は文字列の終わりを示す特殊な値で、文字列には必ず必要です。
この終わりを示す終端が必要なために、10個の箱には9文字しか格納できません。
これも、C言語で良くハマる罠です。
このようにC言語では、chra型の配列に、終端を付加するルールで、文字列を扱います。
〔現在では漢字や、多言語対応のために拡張されていますが、ここでは元来の8ビット限定の文字コードに絞って話を進めます〕
このtextを他の関数に渡す場合、
void set_name(const char* name);
のような関数があるとすれば、
set_name(text);
と書けば渡せます。
さて、受け取る側はあとで使うため、渡された文字列を保存しておきたいため、つぎのようなソースコードを書いたとします。
name.c
--------------------------------------------------
static const char* __name;
void set_name(const char* name)
{
__name = name;
}
渡されたnameを自身の静的変数に保持しています。あたかも文字列全体が保存できたかのように見えますが、これには問題があります。
呼び出した側はつぎの処理のために、set_name(text);を呼び出したあとに、textの内容を書き換えたとします。
すると、あとで使おうと保持しておいたname.cの__nameも、"XYZ"に変わってしまいます。
このような罠にハマってしまったことは、一度や二度、経験があるのではないでしょうか。
つぎのようなソースです。
main.c
--------------------------------------------------
int main(void)
{
char text[10];
text[0] = 'A';
text[1] = 'B';
text[2] = 'C';
text[3] = '\0';
set_name(text);
text[0] = 'X';
text[1] = 'Y';
text[2] = 'Z';
text[3] = '\0';
}
C言語では、配列となっている変数の変数名から、[]部分を取り除き名前だけにすると、その配列の先頭を示すアドレスとなります。
アドレスは、その変数が、どこに格納されているかを示すものです。
この機能のおかげで、配列や文字列をひと固まりとして、スマートに扱えるのですが、混乱も招きます。
set_name(text);では、textの先頭のアドレスしか渡せていません。
受け取る側、set_name()の仮引数nameは、chra型のポインタ変数です。それを保持している静的変数の__nameも、同じchra型のポインタ変数です。
ポインタ変数は、値が格納されている変数の『アドレス』を格納する変数です。
main.cから渡されたtextの先頭アドレスを、ポインタ型の仮引数で受け取り、そのままポインタ型の静的変数にコピーしたにすぎません。
実態はコピーされていないので、元の場所にある実態が変更されてしまうと、保持していたつもりの内容が、変わってしまいます。
![](https://assets.st-note.com/img/1690102936373-IhbYTVDAld.png)
これを回避するにはname.c側にも領域を持ち、実態をコピーして保持しておく必要があります。
つぎのようなソースになります。
C言語には文字列の実態をコピーするには、このように関数を呼び出すか、一文字づつ代入していくループを書かないとできません。
name.c
--------------------------------------------------
static char __name[10];
void set_name(const char* name)
{
strcpy(__name, name);
}
![](https://assets.st-note.com/img/1690103046783-ws3tzFcR4I.png)
さてこの処理だけでは、渡されるnameが9文字以上の場合、問題を起こします。
用意されているのは9文字分の領域だけですが、それを超えてコピーされてしまいます。
用意されている領域を超えた場所は、未知の領域で、そこに何かがコピーされた場合、どのような挙動がまき起こるか、予測不可能です。
今すぐ不具合がは起こらずとも、あとあとになって不具合が起こることも多々あります。
なので実際には、用意された領域を超えてコピーされないような、何らかの措置が必要です。
これは、バッファオーバフローと言われる現象で、C言語が脆弱とされる要因のひとつです。
この話のポイント
・アドレスは、変数が格納されている場所を示す。
・ポインタ変数は、そのアドレスを格納する変数である。
・配列は、メモリ上の連続する場所に、領域がとられ、アドレスは連続する。
・配列は名前だけにすると、その先頭のアドレスになる。
・char型の配列に、最後に'\0'を付けるルールを適応すると、ひと固まりの文字列として扱える。
この記事が気に入ったらサポートをしてみませんか?