C言語基礎|配列の文字列とポインタの文字列の違い

“文字列”は同じ見た目でも、配列は箱、ポインタは住所。だから代入の可否が変わる!

前節で、配列で作った文字列も、ポインタで扱う文字列も、printf の %s で同じように表示できることを確認しました。
でも実はこの2つ、中身の正体がまったく別なんです。

配列の文字列は「文字が入った箱(固定された領域)」、ポインタの文字列は「どこかにある文字列の先頭を指す住所札(付け替え可能)」というイメージ。
この違いが分かると、なぜ配列に文字列を代入できないのかなぜポインタなら代入できるのかがスッと理解できますよ。

まずは全体像:何が違うの?

配列の文字列とポインタの文字列の比較

項目配列による文字列ポインタによる文字列
宣言例char s[] = "ABC";char *p = "123";
変数の正体文字を格納する配列(箱そのもの)先頭文字のアドレスを持つポインタ(住所札)
代入(付け替え)s = "DEF" は不可(エラー)p = "456" は可(OK)
文字の変更s[0] = 'X' は可能p[0] = 'X' は原則危険(後述)
sizeof の結果配列全体のバイト数ポインタのバイト数
主な用途書き換える前提の文字列切り替えたい・参照したい文字列

この表の見方

  • 「配列」は中身(文字)を抱えている
  • 「ポインタ」は中身ではなく「場所」を抱えている
    ここが分かれば勝ちです。

サンプルプログラム

例1:配列の文字列は代入できない(コンパイルエラー)

#include <stdio.h>

int main(void)
{
    char msg[] = "START";

    printf("最初の表示: %s\n", msg);

    msg = "GO";   // エラー:配列には代入できない

    printf("変更後の表示: %s\n", msg);
    return 0;
}

なぜエラー?

配列 msg は「固定された箱」です。箱そのものを別の箱に付け替えるような代入 msg = "GO" はできません。

例2:ポインタの文字列は代入できる(OK)

#include <stdio.h>

int main(void)
{
    char *p = "ON";

    printf("現在の状態: %s\n", p);

    p = "OFF";   // OK:ポインタが指す先を付け替えるだけ

    printf("切り替え後: %s\n", p);
    return 0;
}

ここで起きていること

文字列をコピーしているわけではなく、p が指す先(住所)を切り替えているだけです。

図で理解:配列は「箱」、ポインタは「住所札」

配列の文字列(箱そのもの)

ポインタの文字列(住所札)

図の説明

  • Fig 1 は「文字列の実体が msg の中にある」ことを示しています
  • Fig 2 は「p の中身はアドレスで、指す先を変更できる」ことを示しています

重要ポイント:配列名がポインタっぽく見える理由

C言語では多くの場面で、配列名は先頭要素へのポインタとして扱われます。
でもこれは「配列がポインタになる」のではなく、式として評価されるときにそう見えるだけです。

配列名が“先頭要素へのポインタっぽく”なる場面

表現意味(ざっくり)
msg&msg[0] と同じように扱われる場面が多い
&msg[0]先頭要素へのポインタ
msg = ...これは不可(代入の左側が配列だから)

ここが混乱の根っこ

  • msg は「ポインタのように扱われる」ことがある
  • でも msg 自体は「配列」で、代入で付け替えはできない

文字列リテラルは“消えない”ことが多い(静的な領域)

ポインタで文字列リテラルを切り替えると、前の文字列リテラルはどうなるの?という話も大事です。

ポインタを切り替えると「参照されないリテラル」が出る

図の説明

  • リテラルは「不要になったら自動で破棄」されるイメージではない
  • ポインタが指さなくなっても、リテラル自体は残ることが多い
  • だから「コピーしていない」ことがよりハッキリします

登場する命令(関数)の書式と役割

printf

  • 書式:printf(書式文字列, 引数1, 引数2, ...);
  • 何をする命令?:文字を画面に表示する
  • よく使う指定
    ・%s:char へのポインタを受け取り、'\0' までを文字列として表示する
    ・\n:改行

(補足)ポインタ文字列は書き換えに注意

ポインタ p が文字列リテラルを指しているとき、次は危ないです。

char *p = "ON";
p[0] = 'X';   // 多くの環境で危険

「読み取り専用の可能性がある領域」を触ることになるからです。
読み取り専用として扱うなら、次の形が安心です。

おすすめの宣言

意図宣言
リテラルを読むだけconst char *p = "ON";
自分で書き換えるchar s[] = "ON";

演習問題

問題11-1

次の代入を行うと、表示はどうなるか確認し、理由を説明せよ。
p = "MELON" + 2;

解答例プログラム

#include <stdio.h>

int main(void)
{
    char *p = "APPLE";
    printf("最初: %s\n", p);

    p = "MELON" + 2;
    printf("変更後: %s\n", p);

    return 0;
}

実行結果(例)

最初: APPLE
変更後: LON

解説(やさしく)

  • "MELON" は M E L O N \0 の並び
  • "MELON" + 2 は、先頭から2文字進んだ 'L' を指す
  • %s は指された位置から \0 まで表示する
    だから LON になる、という流れです。

途中から指すイメージ