はじめに
C言語を学んでいると、次の2つのコードが出てきます。
char str1[] = "hello";
char *str2 = "hello";どちらも文字列を扱っているように見えます。
しかし実際には、
- メモリの使い方
- 書き換え可否
- sizeofの結果
- 関数引数での挙動
などが大きく異なります。
この違いは、C言語初心者が最も混乱しやすいポイントの1つです。
実際、組み込み開発でも、
- 文字列を書き換えてクラッシュ
- ROM領域を書き換えようとして異常動作
- 配列だと思ってsizeofを使ったらバグ
といった原因になることがあります。
この記事では、
- char配列とは何か
- charポインタとは何か
- メモリ上で何が違うのか
- なぜ文字列リテラルを書き換えてはいけないのか
- 実務ではどう使い分けるのか
を図付きで分かりやすく解説します。
C言語文法解説シリーズ
本記事は「C言語文法解説シリーズ」の1つです。
C言語の文法を、組み込み開発の視点も交えて解説しています。
C言語文法解説シリーズ一覧はこちら

C言語の文法を基礎から体系的に解説するシリーズです。
先に以下の記事を読むと理解しやすくなります。
👉 「配列とは?」
👉 「ポインタとは?」
👉 「文字列とは?」
まず結論
char str1[] = "hello";
char *str2 = "hello";この2つは、
| 書き方 | 意味 |
|---|---|
char str1[] = "hello"; | 配列の中に文字列をコピーする |
char *str2 = "hello"; | 文字列リテラルを指す |
という違いがあります。
ここを理解することが最重要です。
配列とポインタについてはこちらの記事で解説しています。


char配列とは?
まずはこちら。
char str1[] = "hello";これは、
👉 「文字列を格納するchar配列」
です。
内部的には、
{'h','e','l','l','o','\0'}
という配列になります。
文字列についてはこちらの記事で解説しています。

メモリ上のイメージ
文字が連続したメモリ領域に保存されています。

配列なので書き換え可能
str1 は自分専用の配列領域を持っています。
そのため、
str1[0] = 'H';のように書き換えできます。
実際の例
#include <stdio.h>
int main(void) {
char str1[] = "hello";
str1[0] = 'H';
printf("%s\n", str1);
return 0;
}実行結果:
Hello
charポインタとは?
次はcharポインタについて。
char *str2 = "hello";これは配列ではありません。
👉 「文字列リテラルを指すポインタ」
です。
文字列リテラルとは?
文字列リテラルとは、
"hello"
のように直接書いた文字列のことです。
これはプログラム中の特別な領域に配置されます。
メモリ上のイメージ

str2 は、
👉 文字列の先頭アドレスを持っているだけ
です。
初心者さん見た目はほとんど同じなのに、中身は全然違うんですね



そう。配列は“データそのもの”、ポインタは“場所を指しているだけ”なんだ
文字列リテラルを書き換えてはいけない
ここが超重要です。
例えば:
char *str2 = "hello";
str2[0] = 'H';これは危険です。
なぜ危険なのか
文字列リテラルは、
👉 読み取り専用領域(rodata領域など)
に配置されることが多いためです。
そのため、
str2[0] = 'H';を実行すると、
- クラッシュ
- 異常動作
- 未定義動作
になる可能性があります。
.rodata領域についてはこちらの記事で解説しています。


実務では const を付ける
実際のコードでは、
const char *str = "hello";と書くことが多いです。
constの使い方についてはこちらの記事で解説しています。


const を付ける理由
これは、「この文字列は書き換えません」という意味です。
書き換えようとすると警告
const char *str = "hello";
str[0] = 'H';これはコンパイルエラーや警告になります。
つまり、 バグ防止につながります。
sizeof の違い
初心者がかなりハマるポイントです。
配列の場合
char str1[] = "hello";
printf("%zu\n", sizeof(str1));結果:
6
になります。
理由は:
h e l l o \0
で6バイトだからです。
ポインタの場合
char *str2 = "hello";
printf("%zu\n", sizeof(str2));これは、
👉 ポインタサイズ
になります。
64bit環境なら:
8
になることが多いです。
なぜ違う?
str2 は文字列そのものではなく、
👉 アドレスを保存している変数
だからです。



えっ、文字数じゃないんですか?



ポインタは“住所”だからね。sizeofで見ているのは住所のサイズなんだ
関数引数での違い
これも重要です。
配列を関数に渡すと?
void func(char str[]) {
printf("%zu\n", sizeof(str));
}実はこれ、
👉 配列ではなくポインタ扱い
になります。
なぜ?
C言語では、
👉 配列を関数に渡すと先頭アドレスに変換される
からです。
これを「配列のポインタ変換(配列退化)」と言います。
実際の結果
64bit環境なら:
8
になることが多いです。
つまり、
👉 関数内では配列サイズが分からない
ということです。
実務でよくあるバグ
例えば:
void func(char str[]) {
size_t size = sizeof(str);
// 配列サイズだと思ったら実は8
}こういうバグは本当にあります。
組み込みでもよく事故ります。
配列とポインタは「似ているが別物」
なぜ混乱するのか
C言語では、
str[i]がどちらでも使えるためです。
実は内部的には同じ計算
str[i]は内部的に:
*(str + i)です。
つまり、
👉 ポインタ演算
でアクセスしています。
だから見た目が似る
- 配列
- ポインタ
はかなり似た書き方ができます。
しかし、
👉 メモリ上の意味は違う
ことを忘れてはいけません。
配列とポインタの違いについてはこちらの記事で解説しています。


実務での使い分け
書き換えるなら配列
char str[] = "hello";固定文字列なら const char *
const char *str = "hello";ROM節約でも重要
組み込みでは、
const char *str = "hello";にすると、
👉 ROM配置できる
場合があります。
RAM節約にもつながります。
まとめ
char配列 と charポインタ は、見た目が似ています。
しかし実際には:
| 項目 | char配列 | charポインタ |
|---|---|---|
| 実体 | 配列 | アドレス |
| 文字列コピー | される | されない |
| 書き換え | 可能 | 危険 |
| sizeof | 配列サイズ | ポインタサイズ |
| 実務用途 | 編集用文字列 | 固定文字列 |
という違いがあります。
この違いを理解すると、
- 文字列
- ポインタ
- 配列
- メモリ
- const
- ROM/RAM
の理解が一気につながります。
C言語では非常に重要なポイントなので、しっかり押さえておきましょう。
この記事が参考になった方へ
文字列やポインタをさらに理解したい方は、こちらの記事もおすすめです。
配列を含むC言語の基本文法をこちらの記事で整理しています。


エンジニアとして技術を学ぶことは重要ですが、
キャリアや副業についても同時に考える必要があります。
副業の現実や市場価値、今後のキャリア戦略については、
こちらの記事でまとめています。


技術に関するご相談・開発・自動化ツール作成・記事執筆などのご依頼も承っています。
小さなご相談からでもお気軽にご連絡ください。









コメント