C言語の文字列のポインタの使い方
- 作成日: 2021-10-29
- 更新日: 2024-03-15
- カテゴリ: C言語
C言語の文字列のポインタの使い方
C言語では文字列を扱うことができます。
また、文字列はポインタと一緒に使うことが可能です。
この記事ではC言語の文字列とポインタについて具体的に解説します。
文字列を扱う上で、そのポインタの使い方を覚えるのは避けては通れない道になります。
C言語の文字列とポインタにはいろいろな注意点などもあります。
それらも解説していきます。
まずはC言語の文字列とポインタの関係性についてです。
C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。
C言語の文字列とポインタの関係
C言語で扱える文字列とポインタの関係はどんなものでしょうか?
どういった関連が含まれているのでしょうか?
それは↓のようなものです。
- C言語では文字列はポインタで表せる
C言語では文字列はポインタで表せる
C言語の文字列はポインタで表すことができます。
これはポインタ変数に文字列を代入し、参照したり値を変更できるというものです。
C言語の文字列には複数の種類がありますが、それらの種類の文字列はどれもポインタで表現することが可能です。
C言語の文字列をポインタで表すことで持ち運びが便利になり、コードを書きやすくなります。
しかしポインタを使うので、ポインタ特有の問題はつきまといます。
それはNULL参照や不正なアドレスの参照などです。
C言語の文字列の種類
C言語の文字列の種類についておさらいします。
これは文字列のポインタを扱う場合にも知っておく必要がある知識です。
具体的にはC言語の文字列には↓の2種類があります。
- 文字列定数
- 文字配列
それぞれ具体的に解説します。
文字列定数
文字列定数とは、文字列の定数のことです。
🦝 < まんまです
この文字列の特徴は、書き換え不能という点です。
あとから文字列の文字の値を変更しようとしても、書き換えはできません。
無理に書き換えようとすると、メモリ保護機能が働きセグフォなどになってプログラムがクラッシュする場合があります。
文字列定数はコードでは↓の文字列のことを言います。
"Hello, World!";
ダブルクオーテーションで囲まれた「Hello, World!
」というのが文字列定数になります。
この文字列定数はメモリ上ではテキスト・セグメントに保存されます。
以上が文字列定数の概要です。
文字配列
文字配列とは、文字の配列のことを言います。
これもC言語では文字列と表現します。
🦝 < これもまんまですね
文字配列は単純にchar
型の配列になります。
ワイド文字列の場合はwchar_t
型の配列です。
文字配列はメモリ上ではスタック・セグメントに確保されます。
そのため文字配列の要素は変更することができます。
これは文字配列を定義した後で、文字配列自体を書き換えることができるということです。
コードでは文字配列は↓のように書きます。
char s1[] = "Hello";
char s2[20] = "World";
↑の場合、s1
とs2
が文字配列です。
文字配列自体の初期化は文字列定数で行うことができます。
文字配列の要素数を省略した場合は、自動で要素が確保されます。
要素数を指定した場合はその要素数の配列として確保され、文字配列で先頭から初期化されます。
配列を文字列定数で初期化した場合はナル文字も考慮されます。
つまり要素の確保ではナル文字分も確保されます。
以上が文字配列の概要です。
文字列とセグメント
文字列とポインタを扱う上で、メモリのセグメントの理解は非常に重要になってきます。
セグメントとは「segment
(区画)」のことを言います。
そしてプログラムには↓のセグメントが存在します。
- テキスト・セグメント
- データ・セグメント
- スタック・セグメント
テキスト・セグメント
テキスト・セグメント(text segment
)とは変更不可能(読み出し専用)なデータが置かれるセグメントです。
ここに置かれるデータは文字列定数などがそうです。
実行中のプログラムがここに置かれているデータを変更しようとするとメモリ保護機能が働きセグフォなどが発生します。
データ・セグメント
データ・セグメント(data segment
)とはデータが置かれるセグメントです。
ここに静的変数や大域変数などが置かれます。
ここに置かれているデータは変更可能です。
スタック・セグメント
スタック・セグメント(stack segment
)とは自動変数が置かれるセグメントです。
関数内のローカル変数はここに確保されます。
関数内の文字配列もここに確保されます(static
でない場合)。
ここに置かれているデータは変更可能です。
文字列定数とポインタ
文字列定数をポインタで扱う場合はconst char *
を使います。
#include <stdio.h>
int main(void) {
const char *s = "Hello, World!";
printf("%s\n", s);
// Hello, World!
return 0;
}
↑の場合、s
が文字列定数を保存したポインタです。
この場合、s
も文字列と表現します。
s
はprintf()
などで出力することが可能です。
また、これは文字配列のポインタでも同じですが文字列の要素にアクセスしたい場合は↓のように角カッコを使って配列のようにアクセスします。
#include <stdio.h>
int main(void) {
const char *s = "Hello, World!";
printf("%c\n", s[0]); // H
printf("%c\n", s[1]); // e
printf("%c\n", s[2]); // l
return 0;
}
添え字が要素数の範囲外だった場合は、運が良ければセグフォが発生します。
運が悪ければ何事もなくプログラムが継続します。
constを付けることの意味
文字列定数のポインタ変数にconstを付けていますが、これはなぜでしょうか?
理由は、文字列定数が変更不可な文字列だからです。
const char *
にしておくと、その文字列の要素の変更は不可になります。
変更しようとするとコンパイルエラーになります。
ちなみに文字列定数はchar *
に代入することも出来ます。
char *s = "Hello, World!";
この状態でs
を変更するとセグフォなどになる場合があります。
int main(void) {
char *s = "Hello, World!";
s[0] = '?'; // error
return 0;
}
つまり文字列定数はそもそも変更できないので、それならconst
を付けておいた方が良いという方針です。
ポインタにconst
を付けておいた方がポインタを文字列定数として判断できるので、安全です。
文字列定数のポインタにはconst
を付けるようにしておくクセを持つといいでしょう。
文字配列とポインタ
文字配列のポインタはchar *
やconst char *
を使います。
#include <stdio.h>
int main(void) {
char s[] = "Hello, World!";
char *p = s;
const char *cp = s;
printf("p[%s]\n", p); // Hello, World!
printf("cp[%s]\n", cp); // Hello, World!
return 0;
}
↑の場合、文字配列はs
です。
これをchar *
のp
とconst char *
のcp
に代入しています。
両方ともprintf()
などで出力できます。
文字列定数も文字配列もアドレス演算子を使っていませんが、ポインタに文字列を代入する場合にアドレス演算子は必要ありません。
文字列定数の場合は定数の参照でアドレスが取れますし、文字配列の場合も配列自体の参照でアドレスが取れるからです。
文字配列のポインタから要素にアクセスし、要素の値を変更する場合は↓のようにします。
#include <stdio.h>
int main(void) {
char s[] = "Cat";
char *p = s;
p[0] = 'R'; // 文字配列sの1要素目を変更
printf("%s\n", s); // Rat
printf("%s\n", p); // Rat
return 0;
}
↑の場合、ポインタp
の参照先は文字配列s
です。
ポインタp
の要素を変更するとそのままs
の値も書き変わります。
文字列定数と文字配列のポインタの注意点
文字列定数と文字配列をちゃんぽんしてポインタで使う場合の注意点があります。
それはポインタで扱うと文字列定数と文字配列の見分けがつかない点です。
つまり文字配列のポインタだと思って変更したら実は文字列定数でセグフォになった。
というようなことが起こる場合があります。
これを予防するには、文字列定数のポインタにconst
を付けることです。
const
が付いているポインタは「変更不可能」という認識を持つことで、この手のバグを減らすことができます。
const
は外すことも出来ますが、極力外さないようにしましょう。
関数の引数に文字列のポインタを使う
関数の引数に文字列のポインタを使う場合は↓のようにします。
#include <stdio.h>
void func(const char *s) {
printf("%s"); // Hello, World!
}
int main(void) {
func("Hello, World!");
return 0;
}
↑の例ではconst char *
を使っていますが、もちろんchar *
を使うことも出来ます。
よくあるのは文字配列をポインタで受け取って関数内で変更を加えるという処理です。
その場合は配列の要素数もセットで渡す場合が多いです。
たとえば↓のようにです。
#include <stdio.h>
void set(char *s, size_t size) {
snprintf(s, size, "Hello, World!");
}
int main(void) {
char s[100];
set(s, sizeof s);
printf("%s\n", s);
// Hello, World!
return 0;
}
関連記事
C言語で関数の引数にポインタを渡す【ポインタの値渡し】
関数の返り値に文字列のポインタを使う
関数の返り値に文字列定数のポインタを使う場合は↓のようにします。
#include <stdio.h>
const char *get(int n) {
if (n == 0) {
return "Hello";
} else {
return "World!";
}
}
int main(void) {
printf("%s\n", get(0)); // Hello
printf("%s\n", get(1)); // World!
return 0;
}
文字列定数はコンパイル時にプログラムのテキストセグメントに埋め込まれるので、↑のようにreturn
で返しても参照することができます。
関数内で確保した文字配列はローカル変数なのでreturn
はできません。
正確に言うとできますが、不正なアドレスを返すことになります。
return
したい場合は文字配列をstatic
にする必要があります。
ただその場合はstatic
の特性をよく理解して使ってください。
関連記事
C言語の関数で戻り値にポインタを使う
for文で文字列のポインタを回す
for文で文字列のポインタを回す場合を見てみます。
文字列がナル終端されている場合は↓のように文字列を回すことができます。
#include <stdio.h>
#include <string.h>
int main(void) {
char s[] = "Hello, World!";
// ナル終端まで文字を取り出す
for (char *p = s; *p; p += 1) {
printf("%c\n", *p);
}
// strlenで文字列の長さを取得してfor文を回す
size_t len = strlen(s);
for (size_t i = 0; i < len; i += 1) {
printf("%c\n", s[i]);
}
return 0;
}
どちらの方法もよく使われます。
文字列がナル終端されていない場合は、これらのコードはセグフォなどになる場合があります。
ナル終端とは文字列の末尾の\0
のことです。
文字列定数には暗黙的にこの\0
が埋め込まれています。
関連記事
C言語のfor文の書き方【繰り返し文】
文字列のポインタ配列
文字列のポインタ配列は↓のように書きます。
#include <stdio.h>
int main(void) {
char *pary[] = {
"Hello",
"World!",
NULL,
};
printf("%s\n", pary[0]); // Hello
printf("%s\n", pary[1]); // World!
for (char **p = pary; *p; p += 1) {
printf("%s\n", *p);
}
return 0;
}
↑の場合pary
というのが文字列のポインタ配列です。
文字列のポインタ配列ではブレースを書き、その中にカンマで区切って文字列を書きます。
最後にNULLポインタを入れておくのを忘れないようにしましょう。
これはポインタ配列をfor
文で参照するためです。
文字列のポインタ配列はけっこう色々なシーンで使われるので使い方をマスターしておくと良いかもしれません。
関連記事
C言語の配列の宣言方法: 配列、可変長配列、内部結合な配列、外部結合な配列、ポインタ配列
おわりに
今回はC言語の文字列のポインタについて解説しました。
文字列とポインタは切っても切り離せない関係です。
使い方を覚えておくと吉です。
🦝 < 文字列とポインタは
🐭 < マブダチ