0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C言語のメモリ周りの話がいかに簡単かという話

0
Last updated at Posted at 2025-12-05

概要

私は以前、pythonをメインで触っていました。理由は「とっつきやすい」と周りで評判だったからです。
ところが入社後Cをメインで触るようになって、「こっちの方がわかりやすいじゃん!」と感じるシーンがいくつかありました。
今回はそう感じたポイントの1つとして、Cのポインタとメモリ確保周りの話をします。

(蛇足:「そもそも言語をどう習得するのか」について考察した記事もいずれ書きたいと思っています)

現状、文章と図でintのサイズにずれがある状態になっています。
現在修正中ですのでご容赦頂ければ幸いです。

どういうとき/人に役立つか

  • Cのポインタの概念が腑に落ちていない人
  • pythonなど比較的高級な言語に慣れて、c言語に踏み込みづらさを感じている人

前提知識

他のプログラミング言語の経験/理解が必要です。
c言語の文法もhello, world程度は理解していることが必要かもしれません。

修正履歴

2025/12/08

  • @NyancoRitter 様のコメントを踏まえて、C言語と仮想記憶の関係について補足
  • 言語の学習について追記

目次
1.仮想記憶とは何か
2.アドレス、ポインタとは何か
3.どのあたりがわかりやすいか
4.動的確保とはなにか
5.まとめ

仮想記憶とは何か

※本記事では、OS上のユーザーアプリとして動くCプログラムに限定して話します。1

仮想記憶とは、OSが提供する機能の一つです。
ユーザー側のプログラムに作業領域を提供します。
具体例で説明します。

プログラムには作業領域が必要

hello_100.c
#include<stdio.h>
int main(){
    int i;
    for(i=0;i<100;i++){
        printf("hello!\n");
    }
}

上に記したのは、100回hello!と表示するプログラムです。
そのためにiという整数を使って、「最初に0に設定して」「1回出力したら1増やす」ことで、何回出力したかを制御しています。
このiのように、大抵のプログラムは目的達成のために作業用のデータが必要です。このiが使っているのが作業領域であり、仮想記憶です。

int型は64ビットの整数とコンパイラが判断しますから、2仮想メモリとして8バイト確保して、これを書き換え/参照しながらプログラムが動きます。
なお、これは関数冒頭の「int i;」という行で起こります。宣言というと「プログラムの中で使う変数名を明示する」程度のイメージを持つかもしれませんが、それ以上に「仮想記憶を8バイト確保する、とcpuに命令を出す」という意味を持っています。

1232_1.png

このような作業領域を、OSとCPUが提供しています。
ついでに言うと、「hello」という文字列やcソースをコンパイルしたバイナリ(上図でいう「機械語」に当たる部分)も仮想記憶上に配置されます。3

アドレス、ポインタとはなにか

仮想記憶は16EiBもの長さの一続きの領域になっていて、それぞれのデータ領域には番地が割り当てられています。これをアドレスといいます。しかし自由に使っていいわけではなくて、プロセスが確保した領域を使うことができます。
冒頭のプログラムで使った変数iのアドレスは1000としてみました。実際は実行するたびにOSが決定するため変わります。
c言語には、データが入っているアドレスを取得したり、アドレスを指定してデータにアクセスする方法があります。前者をアドレス演算、後者を関節参照といます。変数の直前に&をつけるとアドレスが取得でき、アドレスの直前に*をつけるとそのデータを参照できます。4

また、アドレスは整数なのですが、アドレスを格納するための型があります。これをポインタ型といいます。int型のポインタはint *のように宣言します。5

1232_2.png

dereference_addressof.c
void main(){
    int i;
    int* x;
    i = 10;
    x = &i;
}

しかしプログラムでアドレスを直打ちしてもアクセスはできません。いや、本当はできてしまうのですが、先ほど述べたように毎回変わってしまうので確実に狙ったデータにアクセスすることはできないです。こんな書き方をすると最悪プログラムが落ちます。

derefer_hard.c
#include<stdio.h>
void main(){
    int i;
    int x;
    i = 10;
    x = *(0x01234567); // iのアドレスを意図しても意図通り動かない
}

どのあたりがわかりやすいか

①頭に図を浮かべやすい

個人的には、すべてのデータにアドレスがあって、データ→アドレス、アドレス→データの取得が自由にできる、というのがわかりやすいと感じます。
冒頭でも少し触れましたが、python等の真新しい言語の多くは仮想記憶自体をプログラマに意識させないような設計思想を持っています。その結果、言語仕様をどう理解すればいいのか路頭に迷ってしまうことがあります。
例を挙げます。

array.py
arr=[1,2,3]
arr_copied=arr
arr_copied[0]=4
print(arr) #[4,2,3]

この動作は、仮想記憶というデータモデルを知らないと異様に見えるのではないでしょうか。
少し調べると、deepcopy()メソッドでやりたいことができることは分かります。
しかしながら、プログラムを書く際には、その言語の設計思想を踏まえて「なぜその動作になるか」理解していることが重要です。6deepcopyを使うことを覚えれば解決する、という単純な話ではありません。

理由は「変数名とはオブジェクトを参照するものである」「代入文はオブジェクトとの束縛を作る」と説明されていますが、私は未だに理解できていません。(参考:公式ドキュメント)

pythonの言語仕様自体を批判しているわけではありません。個人的にわかりづらいと感じてはいますが、思想をちゃんと理解して扱えている人も多いようです。

Cで書くと以下のようになります。

array.c
void main(){
    int arr[3];
    int arr_copied[3];
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;
    arr_copied[0] = arr[0];
    arr_copied[1] = arr[1];
    arr_copied[2] = arr[2];

    arr_copied[0] = 4;
}

宣言文で8*3=24バイト、それぞれ確保する。確保した領域に書き込んでいく。と、すっきりと理解できるのではないでしょうか。
arr_copied = arrのように書くと、アドレスのコピーにしかならないという点もわかると思います。(そもそもコンパイルエラーになりますが)
また、言語仕様が変に抽象化した概念を理解する必要はなく、コンピュータアーキテクチャに根ざして理解できるのもいい点だと思います。

②デバッガで仮想記憶そのものを見られる
Visual studioでは、デバッグ中(ブレークポイントで止まっているとき)に[デバッグ]-[ウィンドウ]-[メモリ]と選択すると、その時点での仮想記憶の状態を見ることができます。
ステップ実行していくとメモリ上の値も都度変わっていき、直観的に理解しやすいと思います。

動的確保とは何か

冒頭の100回helloと出力するプログラムでは、実行する前から4バイトの領域が必要だとわかっていました。しかし、ユーザーの入力によって確保するべき領域サイズが変わるようなプログラムも作りたい場合があると思います。(データベースなど)
そのような場合、プログラム内の変数に入っている値だけの領域を確保する機能をc言語が提供しています。mallocというものです。
1232_3.png

使い方は簡単で、mallocという関数に欲しいサイズを入力して呼び出すと、確保した先頭のアドレスが返答されます。(失敗したら0が返答されます。0というアドレスは存在しないことになっていて、NULLという定数で表します)

この領域は(c言語標準ライブラリを介して)OSが管理しているため、プロセスが終了したら自動で解放されます。

まとめ

  • c言語のメモリ周りの話はいわれているほど7難解ではない。
  • むしろ筆者の主観では、より高級な言語よりわかりやすい側面もある。

わかっていないこと

  • C言語の規格が提供するメモリモデルはどのようなものか。

お読みいただきありがとうございました。

  1. OS上でない環境で動くCプログラムは仮想記憶でないメモリ構造上で動作します。自体はCの言語仕様とは無関係です。

  2. この具体例の想定環境を記しておきます。とはいえ、組み込みとかでなければ通用する話になっているはずです。
    OS:Windows 11 Pro edition 25H2
    コンパイラ:gcc (x86_64-win32-seh-rev2, Built by MinGW-W64 project) 12.2.0
    (※最新じゃないですが、手元の環境がこれでした)

  3. 変数iとの違いは、プログラムがそのアドレスを知ることができない(簡単に知る方法が用意されていない)ことです。

  4. 変数とデータは別のものである、という点に注意が必要です。「i」は文法要素であり、「61」は数値です。考えてみれば当然なのですが、「1000」に*演算子をつければ値が得られるが、「61」に&演算子をつけてもアドレスが得られない、という非対称性があります。

  5. char *型であってもshort *型であっても8バイト確保します。関節参照演算子と紛らわしいですがまったく別物です。

  6. 言語の設計思想を理解することがなぜ重要なのか、はまた別の記事で書ければと思っています。

  7. 以下のような記事に、「Cのポインタはくっそ難しい」「難しくてややこしいところ」との言及が見られました。
    https://qiita.com/nishiwakki/items/6757de7d3e4c1a72d546
    https://qiita.com/Yuzu2yan/items/229df1a2a5f044e9c424

0
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?