ポインタとは?C言語での使い方とメリット・言語別の違いをわかりやすく解説
ポインタとは、別の変数が置かれているメモリアドレスを値として持つ変数です。C言語なら int *p = &score; と書き、*p を通して score そのものを読み書きできます。画面上の矢印(マウスポインター)やプレゼン用のレーザーポインターも日本語では同じ「ポインター」ですが、ここで扱うのはプログラミング用語のほうです。宣言と &/* の書き方、値渡しとの違い、使うメリットと使いどころ、NULL参照を防ぐ書き方、そしてC++・Go・Java・Python・C#で扱いがどう変わるかまでを、手元で実行して出力を確認したコードとともに整理します。
まとめ
- ポインタが持っている値は「変数のメモリアドレス」です。
&でアドレスを取り、*でその先の値を読み書きします。 - 最大の使いどころは、関数から呼び出し元の変数を書き換えることです。値渡しでは呼び出し元は変わりません。
- 大きな構造体ではコピーの実費が効きます。4104バイトで計測すると値渡し約130ns/回に対しポインタ渡しは約2.7ns/回でした(ただしコピーが最適化で消える書き方もあります)。
- 危険の中心はNULL参照と解放済み領域の参照です。
freeの直後にNULLを代入する習慣だけで、事故の多くは判定可能な形に変わります。 - ポインタ型があるのはC・C++・Go・(unsafe時の)C#です。JavaとPythonの変数は参照で、アドレス値を使った読み書きはできません。
以下、定義から順に、C言語の書き方を軸にして具体的なコードで見ていきます。掲載したコードは、最初の1本を除いて main 内の文またはトップレベルの関数定義の抜粋です。C言語のブロックは stdio.h と stdlib.h のインクルードを前提にしており、C++・Go・Pythonのブロックは各見出しで言語を明示します。
ポインタの定義とメモリアドレスの対応関係
変数はメモリ上のどこかに置かれ、その場所には番地(アドレス)が付いています。ポインタは、その番地を値として保持する変数です。「値そのもの」ではなく「値のありか」を持つ、という一点だけが通常の変数との違いです。
ポインタが保持する値=変数が置かれたアドレス
&(アドレス演算子)で変数の番地を取り出し、*(間接演算子・デリファレンス)でその番地にある値へアクセスします。clang 21.0.0(x86_64・macOS)で実行した出力をコメントに添えました。
#include <stdio.h>
int main(void) {
int score = 80;
int *p = &score; /* score が置かれたアドレスを p に入れる */
printf("&score = %p\n", (void *)&score); /* 0x7ff7baf1d288 */
printf("p = %p\n", (void *)p); /* 0x7ff7baf1d288 と同じ */
printf("*p = %d\n", *p); /* 80 */
*p = 95; /* p 経由で score そのものを書き換える */
printf("score = %d\n", score); /* 95 */
return 0;
}
メモリの状態を並べると次のようになります。
変数名 アドレス 中身
---------------------------------------------
score 0x7ff7...d288 80
p 0x7ff7...d278 0x7ff7...d288 ---+
|
p の中身は score のアドレス <--------+
(模式図。実際のアドレスは実行のたびに変わります)
&score と p の表示が一致している点が要です。p は score のコピーではなく、score の住所そのものを持っています。だから *p = 95; と書くと score が 95 になります。なお表示されるアドレスの数値は実行のたびに変わります(OSがアドレス配置をランダム化するため)。数値そのものではなく、2つが一致することだけを見てください。
ポインタ型が型ごとに分かれる理由と加算幅の違い
「ポインタ型とは何か」を一言でいえば、指す先の型を持ったアドレス型です。アドレス自体はどの型でも同じ大きさなのに int * と double * を区別するのは、デリファレンス時に何バイト読むか、そして加算で何バイト進むかがコンパイラに必要だからです。
printf("%zu %zu %zu\n", sizeof(int), sizeof(int *), sizeof(double *));
/* 4 8 8 ... 指す型が何であれ、ポインタ自体の大きさは同じ */
int arr[5] = {10, 20, 30, 40, 50};
int *q = arr;
printf("%d %d\n", *(q + 2), q[2]); /* 30 30 */
printf("%td\n", (char *)(q + 1) - (char *)q); /* 4 = sizeof(int) */
q + 1 が4バイト進むのは int が4バイトだからです。ポインタ演算の単位が「バイト」ではなく「要素」であるこの性質が、配列の添字 q[2] と *(q + 2) を同じ意味にしています。
例外が void * です。指す先の型を持たない汎用ポインタで、malloc の戻り値がこの型にあたります。何バイト読むか決まらないため、デリファレンスできません。加算も標準Cでは不可です(gcc・clangはGNU拡張として1バイト単位の加算を通してしまうので、-pedantic-errors を付けると arithmetic on a pointer to void is a GNU extension として検出できます)。使うときは目的の型へキャストします。
マウスポインター・カーソル・ファイルポインタとの区別
検索では同じ「ポインタ」でも別物が混ざります。画面上の矢印はマウスポインター、文字入力位置を示す点滅する縦棒がカーソルで、どちらもプログラミング用語のポインタとは無関係です。
紛らわしいのは「ファイルポインタ」です。C言語の FILE *fp は確かにポインタ型ですが、実務で「ファイルポインタ」と呼ぶときは ftell が返す読み書き位置を指していることが多く、メモリアドレスの話ではありません。ABCDEFG の7バイトを書いた直後の ftell は 7 を返し、fseek(fp, 2, SEEK_SET) の後は 2 を返して次に読む文字が C になります。同じ語で二つの概念が動いている点は、コードレビューで認識をそろえておく価値があります。
C言語でのポインタの書き方(宣言・&・*)
C言語のポインタでつまずく箇所は、演算子の意味そのものより宣言の読み方に集中しています。ここだけ先に片付けておくと後がずっと楽です。
宣言の * と使用時の * の役割の違い
int *p; の * は「p はポインタである」という型の一部で、*p = 95; の * は「指す先を取り出す」演算子です。綴りは同じでも役割が違います。int *p と int* p はコンパイラにとって完全に同じで、どちらで書いても動作は変わりません。
ただし複数を1行で宣言すると差が出ます。
int* p, q; /* p は int へのポインタ、q はただの int */
printf("%zu %zu\n", sizeof p, sizeof q); /* 8 4 */
* は変数名側に結び付くため、int* p, q; の q はポインタになりません。sizeof q が 4 のままである点がその証拠です。1行1宣言にするか int *p, *q; と書くのが確実です。
未初期化ポインタの危険と -Wuninitialized
初期化していないポインタは、どこを指しているか決まっていません。運が悪ければ他のデータを壊し、運がよければ即座に落ちます。
int *p;
printf("%d\n", *p);
/* warning: variable 'p' is uninitialized when used here [-Wuninitialized] */
clang 21.0.0 はこのコードに -Wuninitialized の警告を出します。ただしこの警告は -Wall に含まれるもので、フラグ無しのビルドでは何も出ません。ビルド設定に -Wall を入れておくかどうかで、検出できるかが変わります。そのうえで、宣言時に NULL か有効なアドレスを必ず入れてください。ここを流したまま書かれたコードが、後述のセグメンテーションフォルトの発生源になります。
値渡しとポインタ渡しの違いと使い分け
関数に変数を渡すと、C言語では常に値のコピーが渡ります。呼び出し元の変数を関数から書き換えたいなら、アドレスを渡すしかありません。これがポインタの最初の存在理由です。
値渡しで呼び出し元が変わらない理由とswap関数
void swap_by_value(int a, int b) { int t = a; a = b; b = t; }
void swap_by_pointer(int *a, int *b){ int t = *a; *a = *b; *b = t; }
int x = 1, y = 2;
swap_by_value(x, y); printf("%d %d\n", x, y); /* 1 2 ... 変わらない */
swap_by_pointer(&x, &y); printf("%d %d\n", x, y); /* 2 1 ... 入れ替わる */
swap_by_value が入れ替えているのは、関数に入った時点で作られたコピーです。関数を抜けるとコピーは消えるので、呼び出し元の x と y は 1 と 2 のままになります。swap_by_pointer は x と y の住所を受け取っているため、書き換えが呼び出し元まで届きます。
入門者が最初に & を書かされる scanf("%d", &n) も同じ理屈です。scanf は読み取った値を呼び出し元の n に書き込む必要があるので、値ではなくアドレスを渡します。& を忘れると、n に入っていた不定の値がアドレスとして解釈され、そこへ書き込みが走ります。
ポインタ渡しと参照渡しの言語別の対応
「参照渡し」という言葉はC言語の解説でもよく出てきますが、C言語に参照型はありません。C言語でやっているのは、正確にはアドレスを値渡しすることです。区別すると次のようになります。
| 渡し方 | 呼び出し側の記法 | 呼び出し元を変更 | NULL を取れるか | 使える言語 |
|---|---|---|---|---|
| 値渡し | f(x) |
不可 | 該当なし | C / C++ / Go / Java / Python / C# |
| ポインタ渡し | f(&x) |
可 | 可 | C / C++ / Go / C#(unsafe) |
| 参照渡し | f(x) / f(ref x) |
可 | 参照自体は不可 | C++ / C#(ref・out) |
C++の参照は初期化時に束縛先が決まり、後から付け替えられません。参照そのものが nullptr になることもありません。「必ず有効な対象がある」ことを型で表明したいなら参照、「無い状態」を表現する必要があるならポインタ、という選び方になります。
C#の ref と out も真の参照渡しで、unsafe を書かずに呼び出し元の変数を更新できます。似た見た目の in は読み取り専用の参照で、代入すると CS8331 になります。用途は書き換えではなく、大きな構造体のコピーを避けることです。
C++で書くと、参照とポインタの差は次のように現れます。
int x = 5;
int &r = x; // 参照: 初期化時に束縛先が決まり、後から付け替えられない
int *p = &x; // ポインタ: nullptr を取れるし、別の変数へ付け替えられる
ポインタを使うメリットと実務での使いどころ
ポインタのメリットは「コピーを避けられる」と説明されがちですが、実測すると効き方には条件があります。まずそこから見たほうが判断を誤りません。
大きな構造体のコピー回避(4104バイトで約130ns対約2.7ns)
4104バイトの構造体を200万回渡し、8反復して計測しました(clang 21.0.0、-O2、x86_64・macOS)。比較のため、受け取った構造体の触り方を変えた関数を2種類用意しています。
struct Big { char buf[4096]; long sum; }; /* sizeof は 4104 バイト */
/* 添字が実行時に決まる: コピーを省けない */
__attribute__((noinline))
long by_value(struct Big b, long k) { return b.sum + b.buf[k & 4095]; }
/* 固定位置のメンバしか触らない: 個別の引数へ分解できる */
__attribute__((noinline))
long by_value_fixed(struct Big b) { return b.sum + b.buf[0]; }
__attribute__((noinline))
long by_pointer(struct Big *b, long k){ return b->sum + b->buf[k & 4095]; }
| 渡し方 | 1回あたり | 実際に動いているもの |
|---|---|---|
| 値渡し(動的添字) | 約131 ns | 4104バイトのコピー |
| 値渡し(固定オフセット) | 約2.8 ns | 必要なメンバだけが渡る |
| ポインタ渡し | 約2.7 ns | 8バイトのアドレス |
約48倍の差が出るのは、コピーが実際に発生した場合だけです。by_value_fixed のように固定オフセットのメンバしか読まない関数では、-O2 が構造体引数を個別のスカラ引数へ分解し、コピーそのものを消してしまいます。「構造体が大きいから値渡しは遅い」は無条件には成り立ちません。
それでも実務でポインタ(またはconst参照)を選ぶ理由は、この分解が効くかどうかが関数の中身に左右され、書き手には見えないからです。添字を1つ動的にしただけで131nsに戻ります。
配列・文字列の先頭アドレス受け渡しと長さ引数
C言語では配列を関数に渡すと先頭要素へのポインタに変換されます。arr と &arr[0] は同じ値です。要素数は失われるので、長さを別引数で渡す必要があります。
int sum(const int *a, size_t n) {
int s = 0;
for (size_t i = 0; i < n; i++) s += a[i];
return s;
}
int arr[5] = {10, 20, 30, 40, 50};
printf("%d %d\n", sum(arr, 5), arr == &arr[0]); /* 150 1 */
この仕様のおかげで、1万要素の配列を渡しても実際に動くのは8バイトのアドレスだけです。文字列(char *)も同じ仕組みで、末尾の '\0' が終端を示すため長さ引数は要りません。
実行時にサイズが決まるデータと malloc/free の対応
入力するまで件数が分からないデータは、malloc で確保した領域をポインタで持ち回ります。
size_t n = 0;
scanf("%zu", &n); /* 件数は実行するまで分からない */
int *data = malloc(n * sizeof(int)); /* malloc の戻り値は void * */
if (data == NULL) return -1;
/* ... 使う ... */
free(data);
data = NULL;
連結リストや木構造のように、自分と同じ型を指すメンバを持つ構造も、ポインタなしには書けません。確保した領域は free するまで残り続けるため、malloc と free を1対1で対応させないとメモリリークとして跳ね返ります。
構造体ポインタとアロー演算子
構造体をポインタで受け取ったときは -> でメンバにアクセスします。u->id は (*u).id の省略記法で、意味は完全に同じです。*u.id と書くと . が先に結合するため error: member reference type 'struct User *' is a pointer になります。括弧を省ける -> が定着したのはこのためです。
struct User { int id; char name[16]; };
void rename_user(struct User *u, const char *name) {
u->id += 1; /* (*u).id と同じ意味 */
snprintf(u->name, sizeof u->name, "%s", name);
}
struct User u = {1, "tanaka"};
rename_user(&u, "suzuki");
printf("%d %s\n", u.id, u.name); /* 2 suzuki */
構造体をポインタで渡すときは、メンバの並び順によってサイズが変わる点も併せて押さえておくと、確保量の見積もりが狂いません。詳しくはメモリアライメントと構造体パディングの解説を参照してください。
NULLポインタと解放済み領域の参照を防ぐ書き方
ポインタの事故はほぼ2種類に収れんします。何も指していないポインタを読むこと(NULL参照)と、すでに解放した領域を読むこと(ダングリングポインタ)です。
一般的なOS上では、NULLの指す先を読むとプログラムはその場で異常終了します。手元で実行すると終了コードは 139 でした。これは 128 + SIGSEGV(11) で、シグナル11による強制終了を意味します。
printf("ここまでは動いた\n");
int *p = NULL;
printf("%d\n", *p); /* NULL の指す先を読もうとして異常終了する */
ここで一点注意があります。このプログラムをパイプやリダイレクトに通すと、直前の「ここまでは動いた」が出力されないまま落ちます。標準出力はリダイレクト先が端末でない場合に完全バッファとなり、異常終了ではフラッシュされないためです。端末で直接実行したときは行バッファなので表示されます。ログをファイルに落としている環境で「この行のログが無いから、まだ到達していない」と読むと、原因箇所を取り違えます。
ダングリングポインタ側の対策は単純で、解放したら即座に NULL を入れることです。
int *p = malloc(sizeof(int));
if (p == NULL) return -1; /* 確保の失敗を必ず見る */
*p = 5;
free(p);
p = NULL; /* 解放したら即 NULL を入れる */
if (p != NULL) *p = 7; /* NULL を入れておけば、この判定で止まる */
解放後のポインタを放置すると、そのアドレスは有効に見えたまま中身だけが無効になります。NULL を入れておけば「無効である」ことが値として判定できる状態に変わり、二重解放も検出しやすくなります。落ちた後の原因追跡はセグメンテーションフォルトの調べ方にまとめました。
C++・Go・Java・Python・C#でのポインタの扱いの違い
「Pythonにポインタはありますか」「Javaのポインタはどこにありますか」という疑問は、言語ごとに何が隠されているかを整理すると片が付きます。
| 言語 | ポインタ型 | アドレス取得 | ポインタ演算 | 手動解放 |
|---|---|---|---|---|
| C | あり(int *) |
& |
可 | free が必要 |
| C++ | あり+スマートポインタ | & |
可 | スコープで自動化可 |
| Go | あり(*T) |
& |
不可 | 不要(GC) |
| Java | なし(参照のみ) | 不可 | 不可 | 不要(GC) |
| Python | なし(参照のみ) | 不可 | 不可 | 不要(GC) |
| C# | unsafe 時のみ |
&(unsafe) |
可(unsafe) | 不要(GC)※注 |
※C#で Marshal.AllocHGlobal や NativeMemory.Alloc を使ってアンマネージドメモリを確保した分は、GCの対象外なので手動解放が必要です。
Goのポインタ制約(演算不可・定数アドレス取得不可)
Goのポインタはゼロ値が nil で、構造体には -> がなく . だけで自動的にデリファレンスされます。
type Point struct{ X, Y int }
x := 42
p := &x
*p = 100
fmt.Println(x) // 100
var np *int
fmt.Println(np == nil) // true ... ポインタのゼロ値は nil
pt := &Point{X: 1, Y: 2}
pt.X = 9 // Go に -> は無く、自動でデリファレンスされる
C言語との決定的な差は2点です。ポインタ演算ができず、定数のアドレスも取れません。go1.26.5 で確かめたエラーメッセージがこれです。
const C = 7
_ = &C
// invalid operation: cannot take address of C (untyped int constant 7)
x := 1
p := &x
_ = p + 1
// invalid operation: p + 1 (mismatched types *int and untyped int)
ポインタ演算を禁じることで、境界外アクセスという事故の型そのものをGoは言語から取り除いています。unsafe.Pointer を経由して uintptr に変換すれば演算自体は書けますが、公式ドキュメントが説明するとおり uintptr は参照ではなく単なる整数で、GCはその値を追跡もオブジェクトの生存保証もしません。通常のコードで使う場面はありません。
JavaとPythonの参照とアドレス値の不可視性
Java言語仕様(JLS SE25, 4.3.1)は参照値について「The reference values (often just references) are pointers to these objects」と明記しています。つまり実体はポインタです。しかしアドレス値を取り出す構文がなく、演算もできないため、書き手が触れるのは「同じオブジェクトを指しているかどうか」だけになります。null の参照先を呼べば NullPointerException が発生し、C言語の異常終了に相当する事象が捕捉可能な例外として表面化します。
Pythonも同じ構図です。変数はオブジェクトへの参照で、id() が返す識別値について公式ドキュメントは「これはオブジェクトのメモリアドレスです」とCPython実装詳細として説明しています。整数として得られるだけで、そこから読み書きする手段は標準構文にありません。
x = [1, 2]
y = x
print(x is y) # True ... 同じオブジェクトを指している
y.append(3)
print(x) # [1, 2, 3] ... x 側にも見える
print(hex(id(x))) # 0x106b5de40
リストを別名に代入して片方を変更すると両方に見えるのは、値がコピーされていないからです。C言語のポインタを理解していると、この挙動は「同じアドレスを2つの名前が指している」として素直に読めます。
C#のunsafeコンテキストとAllowUnsafeBlocksの条件
C#はポインタ型を持っていますが、既定では使えません。Microsoftの公式リファレンス「安全でないコード、データへのポインター、および関数ポインター」(2026年6月17日更新)は、ポインタ型の宣言・アドレス取得・デリファレンスがいずれも unsafe コンテキストを必要とし、コンパイル時に AllowUnsafeBlocks オプションが要ると述べています。
ガベージコレクタによる移動を防ぐ fixed のピン留めが必要になるのは、参照先がマネージドヒープ上のオブジェクトである場合です。スタック上のローカル変数を指すだけなら不要で、同ドキュメントの int number = 1024; byte* p = (byte*)&number; という例にも fixed は出てきません。
なお同ドキュメントは、C# 15 と .NET 11 でプレビュー中の更新モデルではポインタ型の宣言・& によるアドレス取得・fixed が安全なコードでも許可され、unsafe が必要なのはデリファレンスなど実際にメモリへ触れる操作に絞られる、としています。プレビュー段階の仕様なので、採用判断は正式リリースを待つのが妥当です。
GoからC#までの4言語で free が原則不要なのは、いずれもガベージコレクタが回収を担っているからです。仕組みはガベージコレクション(GC)の解説にまとめています。
ポインタを使うべきでない場面
最も避けたいのは、C++で所有権を持つ生ポインタを新規に書くことです。new と delete を対で管理する設計は、例外が飛んだ経路で簡単に破綻します。std::unique_ptr は既定のデリータであれば生ポインタと同じ8バイトで、スコープを抜ければデストラクタが走ります。参照カウントが要る場面だけ16バイトの std::shared_ptr を選べば十分です。生ポインタは「所有しない参照」としてだけ使う、という線引きが実務的です(以下はC++)。
#include <memory>
struct Node { int v; };
auto up = std::make_unique<Node>(); // sizeof は 8(生ポインタと同じ)
auto sp = std::make_shared<Node>(); // sizeof は 16(参照カウントを持つ)
次に、int のような小さな値を「効率のため」とポインタで渡すのも避けてください。ここは実測すると差がありません。noinline を付けた関数へ2億回渡した結果は、値渡し2.4〜2.7ns、ポインタ渡し2.7nsでした。つまり理由は性能ではなく設計側にあります。ポインタで受けた瞬間にNULLの可能性が型に入り込み、呼び出し側にも & の記述が要り、関数本体は間接参照で読みにくくなります。書き換えの必要がないなら値で渡してください。
GoやC#でポインタ演算をしたくなったときも、設計を疑うべきです。Goが演算を禁じ、C#が unsafe の壁を置いているのは、その操作がGCの前提と衝突するからです。ネイティブAPIとの相互運用のように理由が明確な場合を除き、スライスや Span<T> で書けないかを先に検討してください。
逆にポインタが最短の答えになるのは、関数から呼び出し元の変数を書き換えるとき、実行時にサイズが決まるデータを持ち回るとき、そして自己参照する構造(リスト・木・グラフ)を組むときです。どれにも当てはまらないなら、まず値渡しで書いてみてください。速度が問題なら、インライン関数のような別の最適化手段が効くこともあります。
よくある質問
ポインタとメモリアドレスは同じものですか?
違います。メモリアドレスは「番地」という値そのもので、ポインタはその番地を格納する変数です。&score という式が返すのがアドレス、それを受け取る int *p がポインタにあたります。ポインタ変数自身もメモリ上のどこかに置かれるため、&p と書けばポインタ変数のアドレスも取れます。この入れ子が、次の質問のダブルポインタにつながっていきます。
ポインタのポインタ(ダブルポインタ)はどんなときに使いますか?
関数の中から、呼び出し元のポインタ変数そのものを書き換えたいときです。確保したメモリの先頭アドレスを引数経由で返す関数が典型例で、char * を書き換えたいので引数は char ** になります。
int alloc_buf(char **out, size_t n) {
char *buf = malloc(n);
if (buf == NULL) return -1;
*out = buf; /* 呼び出し元のポインタ変数そのものを書き換える */
return 0;
}
char *buf = NULL;
alloc_buf(&buf, 8); /* &buf の型は char ** */
戻り値でポインタを返せる設計ならダブルポインタは不要です。戻り値をエラーコードに使いたい、あるいは複数のポインタを一度に更新したい場合に選択肢へ入ります。三重以上のポインタが必要になったら、設計を見直したほうが早いことがほとんどです。
関数ポインタはどんな場面で使いますか?
処理そのものを引数として差し替えたいときです。qsort に比較関数を渡す、状態ごとの処理をテーブルで持つ、コールバックを登録する、といった用途が定番です。宣言は int (*op)(int, int); のように、関数名の位置に (*変数名) を置きます。
int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }
int (*op)(int, int) = add;
printf("%d\n", op(3, 4)); /* 7 */
op = mul;
printf("%d\n", op(3, 4)); /* 12 */
int *op(int, int) と括弧を外すと「intへのポインタを返す関数」という別物になります。同じ理屈で int *p[5] は「intへのポインタが5個並んだ配列」、int (*p)[5] は「int5個の配列へのポインタ」です。括弧の有無で意味が変わる型は、この2組を覚えておけば大半が読めます。
const int *p と int *const p は何が違いますか?
const int *p は指す先が読み取り専用で、ポインタ自体は別の変数へ付け替えられます。int *const p はその逆で、ポインタが固定される代わりに指す先は書き換え可能です。const が * の左にあるか右にあるかで読み分けます。
int a = 1, b = 2;
const int *p1 = &a; /* 指す先が読み取り専用。付け替えは可 */
int *const p2 = &a; /* ポインタ自体が固定。指す先の書き換えは可 */
p1 = &b; /* OK */
*p2 = 10; /* OK */
*p1 = 5; /* error: read-only variable is not assignable */
p2 = &b; /* error: cannot assign to variable 'p2' with const-qualified type */
関数の引数で「受け取ったデータを変更しない」と表明する用途では、前者の const int * を使います。
Goで定数のアドレスが取れないのはなぜですか?
Goの定数はコンパイル時に値へ展開され、実行時にメモリ上の場所を持たないためです。&C と書くと invalid operation: cannot take address of C というコンパイルエラーになります。アドレスが必要なら、いったん変数へ代入してからそのアドレスを取ります。C言語の const int c = 7; は実行時に場所を持つ変数なので &c が取れる点が、同じ「定数」でも扱いの分かれるところです。