7839

雑草魂エンジニアブログ

【C/C++】C言語文法の復習 変数・メモリ・コンパイラ

C言語は大学の授業でやったぐらいで10年ぶりの学び直し。最近は、RubyやTypeScriptばかりやっていたので、すっかり忘れていた。C言語において、気になった部分を備忘録として書き残しておく。

今回は、一週間で身につくC言語の基本を教材として使わせていただいた。これが無料公開されているなんて、本当に感謝しかない。

型変換(キャスト)

変数の前に、(型)を置く場合には、型変換を行うことができる。

int i1=39, i2;
double d1=1.23, d2;
i2 = (int)d1;
d2 = (double)i1;

関数のプロトタイプ宣言

関数は先に定義しておく必要がある。
そのため、プロトタイプ宣言で型を先に宣言しておく。

#include <stdio.h>
void foo();  // プロトタイプ宣言
int main()
{
  // main処理
}
void foo()
{
  // foo関数の処理
}

変数

C言語の変数には、以下の3種類がある。

  1. 外部変数(グローバル変数
  2. 自動変数(ローカル変数)
  3. 静的変数(ローカル変数)

外部変数(グローバル変数

  • 関数外で定義され、定義以降のどの関数からでも使用可能な変数
  • 初期化はプログラム開始処理の前に一度だけ行うことができる
  • 明示的に初期化を行わない場合は、初期値は 0 になる
  • 静的領域に記憶される(プログラム実行とともに生成され、プログラム終了と共に消える)

自動変数(ローカル変数)

  • 関数内部で宣言され、宣言された関数の中でのみ使用可能な変数
  • 先頭にauto指定子をつける(通常省略されている)
  • メモリの中でもスタック領域に記憶される(関数の処理の終了と共にメモリから消去される)

静的変数(ローカル変数)

  • 先頭にstatic指定子をつける
  • 宣言された関数の中でのみ使用可能である。そのため、外部からexternでアクセスできない。
  • 静的領域に記憶される(プログラム実行とともに生成され、プログラム終了と共に消える。ゆえに、関数の中で値を保持することができる。)
  • プログラム開始処理の前に一度だけ、初期化を行うことができる

静的領域の場合、ずっとメモリーを確保してしまうので、注意が必要。

定数の定義

#defineマクロの使用

#define PI 3.14

PIという文字列が出てくれば、このソースの中では、3.14として扱うことができる。

メモリの4領域

f:id:serip39:20220115161506p:plain

名前 説明
プログラム領域 プログラム(マシン語)が格納される場所。
静的領域 グローバル変数やstatic変数が置かれる領域。この静的領域の大きさはプログラム実行時に確保され、プログラム実行中にサイズが変化することがなく、固定サイズである。
ヒープ領域 mallocなどを用いて、動的に確保された変数が置かれる領域。静的領域とは違い、ヒープ領域のサイズはプログラム実行中に変化する。メモリのアドレスは、小さい方から確保される。
スタック領域 ローカル変数などが置かれる領域。メモリのアドレスは、大きい方から確保される。変数を定義しすぎると、スタックオーバーフローになることもあるので注意が必要。

ファイル分割

ファイルは、以下の2種類がある。

  • ".h"(ヘッダファイル):プロトタイプ宣言を記述しておく
  • ".c"ファイル(ソースファイル)

外部ファイルやライブラリを読み込む場合は、以下の方法を用いる。

  • #include <ヘッダファイル名> :ライブラリファイル(.lib)参照(コンパイル済み)
  • #incldue "ヘッダファイル名":自作のソースファイル参照(コンパイル必要)

ヘッダファイルの書式

#ifndef _(大文字で記述したファイル名)_H_
#define _(大文字で記述したファイル名)_H_

プロトタイプ宣言;

#endif // _(大文字で記述したファイル名)_H_

#ifndef / #define / #endif

  • マクロ
  • C言語そのものの文法とは無関係
  • コンパイラに指令を与えるもの
  • 二重インクルードの防止に使われる

グローバル変数の読み込みに関して

extern修飾子を使って、別ファイルで宣言されていることを明記する

extern <データ型> <変数名>

Cコンパイラの仕組み

プログラムを実行するためには、C言語で書かれたソースコードをコンピュータが理解できる機械語に変換する必要がある。ソースコードから、実行可能なファイルを作成することを「ビルド」という。

f:id:serip39:20220115183804p:plain

0. 静的解析

ビルドを開始すると、まずソースコードの静的解析が行われる。静的解析では、文法や構文の誤り(バグ)を検出する。バグがあった場合は、エラー内容を通知して、処理がストップする。

1. プリプロセッサ

名前の通り、コンパイルのための前処理が行われる。

  • #includeしているヘッダファイルの中身をソースファイルにコピーする
  • #defineマクロの展開
  • コメントの削除
gcc -E main.c

-Eオプションをつけることで、プリプロセッサまでを実行できる。

2. コンパイラ

C言語(高水準言語)で書かれたファイルをアセンブラ言語のファイルに変換する。 アセンブラ言語とは、機械語を扱いやすいように書き直した低水準言語である。

この時点では、外部のファイルの関数などを参照している場合、プリプロセッサでヘッダファイルを読み込んで、プロトタイプ宣言はできているものの、外部参照が未解決の状態のままとなっている。(外部参照を解決するのは、リンカの部分。)

gcc -S main.c

-Sオプションをつけることで、コンパイラまでを実行できる。main.sというアセンブラファイルが生成される。

3. アセンブラ

コンパイラで生成されたアセンブラ言語のファイルを機械語のファイルに変換する。生成される機械語のファイルは、オブジェクトファイルと呼ばれる。

gcc -c main.c

-cオプションをつけることで、アセンブラまでを実行できる。main.oというオブジェクトファイルが生成される。

4. リンカ

各オブジェクトファイルやライブラリを1つにまとめる作業(リンク)が行われ、最終的に「実行ファイル」が完成する。

リンカは、main関数のあるオブジェクトファイルから、コンパイラの部分で未解決の外部参照を探索し、必要な情報を全て集めて、まとめる作業を行う。

gcc main.c -o a.out

-oオプションで、実行ファイル名を指定できる。

まとめ

C言語を久しぶりに触ったが、メモリなどの低レイヤーをきちんと理解したりしなくちゃいけないので、これはこれでまた楽しいなと思えた。