変数は、プログラムが計算に必要な値を一時的に保存するための箱のようなものです。
C言語では変数に必ず型が付き、確保されるメモリの大きさや表現できる値の範囲が型で決まります。
本記事では、宣言から初期化、代入、命名、型、スコープまでを、実行結果つきの具体例とともにやさしく解説します。
C言語の変数とは?
C言語の変数は、メモリ上の特定の領域に付けた名前(識別子)です。
変数には必ず型があり、型によって、その領域の大きさや表現できる値、小数の有無などが決まります。
プログラムはこの変数に値を代入し、必要に応じて読み出し、計算や表示に利用します。
Cでは「どの型の値を、どれくらいのサイズで、どのように扱うか」を明示することが重要です。
例: 単純な変数の利用
#include <stdio.h>
int main(void) {
int score = 80; // 整数の点数
double rate = 0.75; // 小数の割合
char grade = 'B'; // 1文字の評価
printf("score=%d, rate=%.2f, grade=%c\n", score, rate, grade);
return 0;
}
score=80, rate=0.75, grade=B
変数の宣言・初期化・代入(基本構文)
変数の宣言の書き方
変数は「型名 変数名;」の形で宣言します。
必要なら宣言と同時に初期化もできます。
読みやすさのため、1行に1つずつ宣言するのが初心者にはおすすめです。
#include <stdio.h>
int main(void) {
int count; // 整数の箱を用意(まだ中身は未定)
double price; // 値段などの実数
char initial; // 1文字
// 複数を1行で宣言することも可能(可読性には注意)
int x = 0, y = 1, z; // zはこの時点では未初期化
// 宣言しただけでは安全に使えないため、使う前に値を入れる
count = 0;
price = 120.5;
initial = 'C';
printf("count=%d, price=%.1f, initial=%c, x=%d, y=%d\n",
count, price, initial, x, y);
return 0;
}
count=0, price=120.5, initial=C, x=0, y=1
よくある注意
未初期化のローカル変数(上のzなど)を読み出すのは未定義動作です。
プログラムがクラッシュしたり予期せぬ値が出るので、必ず初期化してから使います。
初期化の基本
初期化は、宣言と同時に最初の値を与えることです。
ローカル変数は自動的に初期化されませんが、ファイルスコープやstatic
な変数は自動的に0初期化されます。
#include <stdio.h>
// ファイルスコープの変数は自動で0初期化される
int global_count;
int main(void) {
int local_count = 0; // 明示的に初期化
// int uninit; // これをそのまま使うのは未定義動作(使わない)
printf("local_count=%d, global_count=%d\n", local_count, global_count);
return 0;
}
local_count=0, global_count=0
初期化の利点
意図しないゴミ値の混入を防ぎ、デバッグが容易になります。
また、意味のある初期値を与えることでコードの意図が明確になります。
代入と再代入
代入は、すでに宣言済みの変数に値を入れる操作です。
変数は後から別の値に置き換える(再代入)ことができます。
#include <stdio.h>
int main(void) {
int n = 5; // 初期化
printf("初期値 n=%d\n", n);
n = 12; // 再代入(上書き)
printf("再代入後 n=%d\n", n);
n = n + 3; // 現在の値を使った計算代入
printf("計算後 n=%d\n", n);
return 0;
}
初期値 n=5
再代入後 n=12
計算後 n=15
命名規則と予約語
変数名(識別子)にはアルファベット、数字、アンダースコア(_)が使えますが、最初の文字に数字は使えません。
大文字・小文字は区別されます。
また、言語仕様で使われるキーワード(予約語)は変数名として使えません。
#include <stdio.h>
int main(void) {
int Value = 1; // 大文字V
int value = 2; // 小文字v (別の変数)
// int 2nd = 0; // 先頭が数字でエラー
// int for = 0; // forは予約語でエラー
printf("Value=%d, value=%d\n", Value, value);
return 0;
}
Value=1, value=2
主なキーワード(予約語)の例
以下は変数名に使えない代表的なキーワードです。
キーワード | 役割の例 |
---|---|
int, char, float, double | 型の指定 |
void | 型がないことの指定 |
if, else, switch, case, default | 条件分岐 |
for, while, do, break, continue | 反復処理 |
return | 関数の戻り |
const, static, extern, volatile, register | 記憶クラスや修飾 |
signed, unsigned, short, long | 整数型の修飾 |
struct, union, enum, typedef | 複合型や別名 |
sizeof | オブジェクトの大きさ |
constで再代入を防ぐ
const
を付けると、その変数は読み取り専用になり、再代入が禁止されます。
意図しない変更を防ぐのに有効です。
#include <stdio.h>
int main(void) {
const int DAYS_IN_WEEK = 7;
printf("1週間は%d日です\n", DAYS_IN_WEEK);
// DAYS_IN_WEEK = 8; // コンパイルエラー: constな変数は再代入不可
return 0;
}
1週間は7日です
ポインタとconstの注意
ポインタと併用すると「指す先が定数」なのか「ポインタ自体が定数」なのかが異なります。
入門段階では、まずスカラー型(intやdouble)に対するconst
から慣れると良いです。
変数の仕組み
値を保存するしくみ
変数はメモリ上の連続したバイト列に保存されます。
型は「そのバイト列をどう解釈するか」を決めます。
例えばint
は通常4バイトで、整数として解釈され、double
は通常8バイトで倍精度の実数として解釈されます。
メモリのどこに置かれるかはスコープと記憶期間によって異なります。
識別子(名前)とメモリの関係
ソースコード上の変数名は、人間が扱いやすくするためのラベルです。
実行時には、それぞれの変数に固有のアドレスが割り当てられます。
アドレス演算子&
でその位置を観察できます。
#include <stdio.h>
int main(void) {
int a = 10;
double b = 3.14;
char c = 'X';
printf("&a=%p, &b=%p, &c=%p\n", (void*)&a, (void*)&b, (void*)&c);
return 0;
}
&a=0x7ffeefbff56c, &b=0x7ffeefbff560, &c=0x7ffeefbff55f
アドレスの見え方
上記の値は実行のたびに変わることがあります。
これはOSやコンパイラ、スタック/ヒープ配置の仕組みによるものです。
リテラルとの違い
リテラルは、プログラムに直接書かれた固定の値です。
例えば42
、3.14
、'A'
、"hello"
などです。
変数は後から値が変わりますが、リテラル自体は変わりません。
#include <stdio.h>
int main(void) {
// 文字列リテラルを配列にコピーすると書き換え可能
char s[] = "hello";
s[0] = 'H';
printf("配列s: %s\n", s);
// 文字列リテラルへのポインタ。多くの処理系で書き換え禁止領域に置かれる
const char *p = "world";
// p[0] = 'W'; // 未定義動作: 絶対に書き換えない
printf("ポインタp: %s\n", p);
// 数値・文字リテラルの例
int n = 42; // 42 は整数リテラル
char ch = 'A'; // 'A' は文字リテラル(1文字)
// "A" は長さ2の文字列リテラル('A'と終端'\0')で、charと異なる
printf("n=%d, ch=%c\n", n, ch);
return 0;
}
配列s: Hello
ポインタp: world
n=42, ch=A
C言語の型の基礎と型変換
基本の型(int, char, float, double)
Cの基本的な数値型は、おおまかに整数型と浮動小数点型に分かれます。
int
は整数、char
は1文字(実体は小さな整数)、float
は単精度小数、double
は倍精度小数です。
代表的な用途とprintfの指定子
型 | 主な用途 | printfの指定子 |
---|---|---|
int | 整数の計算、カウンタ | %d |
char | 文字、バイトデータ | %c (文字として) / %d (数値として) |
float | 単精度の小数 | %f (引数はdoubleに昇格) |
double | 倍精度の小数 | %f, %e, %g |
#include <stdio.h>
int main(void) {
int i = 42;
char ch = 'A';
float f = 0.1f; // fサフィックスでfloat型
double d = 3.14159;
printf("i=%d, ch=%c, f=%.6f, d=%.5f\n", i, ch, f, d);
// charを整数として見ることも可能
printf("ch as int=%d\n", ch);
return 0;
}
i=42, ch=A, f=0.100000, d=3.14159
ch as int=65
signed/unsignedの違い
整数型は符号付き(signed)と符号なし(unsigned)があり、表現できる範囲や演算の挙動が異なります。
符号なしは負数を表現できませんが、同じビット数ならより大きな正の範囲を扱えます。
#include <stdio.h>
#include <limits.h>
int main(void) {
unsigned int u = 0;
u -= 1; // 0から1を引くと桁あふれして最大値(2^32-1)へ
printf("u=%u (UINT_MAX=%u)\n", u, UINT_MAX);
int s = -42;
unsigned int us = (unsigned int)s; // 符号なしへ変換
printf("s=%d を unsigned に変換すると us=%u\n", s, us);
int si = -1;
unsigned int ui = 1;
printf("si < ui の結果は %d (注意: 符号なしへ昇格して比較)\n", (si < ui));
return 0;
}
u=4294967295 (UINT_MAX=4294967295)
s=-42 を unsigned に変換すると us=4294967254
si < ui の結果は 0 (注意: 符号なしへ昇格して比較)
混在に注意
符号付きと符号なしを混在させた演算や比較は、暗黙の型変換により予想外の結果になりがちです。
不用意に混ぜない、もしくは明示的にキャストして意図を明確にします。
型のサイズと範囲
型のサイズは処理系依存ですが、一般的な64ビット環境(LP64)では次のような傾向があります。
実際の環境はsizeof
やlimits.h
で確認します。
型 | 典型的なサイズ | 代表的な範囲の例 |
---|---|---|
char | 1バイト | signed char: -128〜127 / unsigned char: 0〜255 |
short | 2バイト | -32768〜32767 |
int | 4バイト | 約 -2.1e9 〜 2.1e9 |
long | 8バイト(LP64) | 約 -9.22e18 〜 9.22e18 |
long long | 8バイト | 約 -9.22e18 〜 9.22e18 |
float | 4バイト | 約 ±3.4e38、7桁程度の精度 |
double | 8バイト | 約 ±1.7e308、15〜16桁の精度 |
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main(void) {
printf("sizeof(char)=%zu, SCHAR_MIN=%d, SCHAR_MAX=%d, UCHAR_MAX=%u\n",
sizeof(char), SCHAR_MIN, SCHAR_MAX, UCHAR_MAX);
printf("sizeof(short)=%zu, SHRT_MIN=%d, SHRT_MAX=%d\n",
sizeof(short), SHRT_MIN, SHRT_MAX);
printf("sizeof(int)=%zu, INT_MIN=%d, INT_MAX=%d\n",
sizeof(int), INT_MIN, INT_MAX);
printf("sizeof(long)=%zu, LONG_MIN=%ld, LONG_MAX=%ld\n",
sizeof(long), LONG_MIN, LONG_MAX);
printf("sizeof(long long)=%zu, LLONG_MIN=%lld, LLONG_MAX=%lld\n",
sizeof(long long), LLONG_MIN, LLONG_MAX);
printf("sizeof(float)=%zu, FLT_MIN=%e, FLT_MAX=%e\n",
sizeof(float), FLT_MIN, FLT_MAX);
printf("sizeof(double)=%zu, DBL_MIN=%e, DBL_MAX=%e\n",
sizeof(double), DBL_MIN, DBL_MAX);
return 0;
}
sizeof(char)=1, SCHAR_MIN=-128, SCHAR_MAX=127, UCHAR_MAX=255
sizeof(short)=2, SHRT_MIN=-32768, SHRT_MAX=32767
sizeof(int)=4, INT_MIN=-2147483648, INT_MAX=2147483647
sizeof(long)=8, LONG_MIN=-9223372036854775808, LONG_MAX=9223372036854775807
sizeof(long long)=8, LLONG_MIN=-9223372036854775808, LLONG_MAX=9223372036854775807
sizeof(float)=4, FLT_MIN=1.175494e-38, FLT_MAX=3.402823e+38
sizeof(double)=8, DBL_MIN=2.225074e-308, DBL_MAX=1.797693e+308
補足
char
が符号付きか符号なしかは処理系依存です。
必要に応じてsigned char
またはunsigned char
を明示します。
型変換(暗黙/明示)
演算では、より表現力の高い型へ自動的に拡張されることがあります(暗黙の型変換)。
整数同士の割り算は小数点以下が切り捨てられることに注意します。
意図を明確にするためにキャスト(明示的変換)を使うこともあります。
#include <stdio.h>
int main(void) {
int a = 1, b = 2;
// 整数同士の割り算は切り捨て
double avg1 = (a + b) / 2; // 1 になる
// どちらかをdoubleにすると小数で計算
double avg2 = (a + b) / 2.0; // 1.5
double avg3 = ((double)a + b) / 2; // 1.5
double x = 3.99;
int xi = (int)x; // 小数点以下切り捨て(0方向)
int big = 300;
unsigned char uc = (unsigned char)big; // 300 % 256 = 44 (典型的な8ビットcharの場合)
printf("avg1=%.1f, avg2=%.1f, avg3=%.1f\n", avg1, avg2, avg3);
printf("x=%.2f を int にすると %d\n", x, xi);
printf("big=%d を unsigned char にすると %u\n", big, uc);
return 0;
}
avg1=1.0, avg2=1.5, avg3=1.5
x=3.99 を int にすると 3
big=300 を unsigned char にすると 44
注意すべきポイント
- 整数の除算の切り捨ては典型的な落とし穴です。平均を取るときは分母か分子を
double
にします。 - 縮小方向の変換(大きい型→小さい型)は情報を失います。安全性が必要な場面では範囲チェックを行います。
スコープと記憶期間(寿命)の理解
ブロックスコープ(ローカル変数)
ブロック{ ... }
内で宣言した変数は、そのブロック内でのみ有効です。
ブロックを抜けると寿命が尽き、メモリは解放されます。
外側と同名の変数を宣言すると内側が優先されます(シャドーイング)。
#include <stdio.h>
int main(void) {
int x = 10;
printf("外側のx=%d\n", x);
{
int x = 20; // 外側のxを隠す(シャドーイング)
printf("内側のx=%d\n", x);
} // ここで内側のxは寿命終了
printf("再び外側のx=%d\n", x);
return 0;
}
外側のx=10
内側のx=20
再び外側のx=10
グローバル変数の扱い
関数の外(ファイルスコープ)で宣言した変数は、同一ファイル内のすべての関数から参照できます。
寿命はプログラムの開始から終了までです。
#include <stdio.h>
int counter = 0; // グローバル変数
void inc(void) {
counter++; // どの関数からも参照・更新できる
}
int main(void) {
inc();
inc();
printf("counter=%d\n", counter);
return 0;
}
counter=2
別ファイルから使う場合の例
extern
で外部の定義を参照できます。
// counter.c
int counter = 0;
// main.c
#include <stdio.h>
extern int counter; // 別ファイルの定義を参照
void inc(void) { counter++; }
int main(void) {
inc();
printf("counter=%d\n", counter);
return 0;
}
staticの効果(スコープと寿命)
static
には2つの主要な効果があります。
関数内static: ローカルスコープだが寿命はプログラム全体
関数内でstatic
を付けた変数は、その関数からしか見えませんが、値は呼び出し間で保持されます。
#include <stdio.h>
void tick(void) {
static int s = 0; // 初回だけ初期化、以後値が保持される
s++;
printf("tick: %d\n", s);
}
int main(void) {
tick();
tick();
tick();
return 0;
}
tick: 1
tick: 2
tick: 3
ファイルスコープstatic: 内部リンケージ(ファイル内限定)
関数や変数をファイル内限定の公開範囲にします。
別ファイルからextern
で参照できなくなります。
// module.c
static int hidden = 0; // このファイル内でのみ有効
static void helper(void) { // この関数もこのファイル内だけで呼べる
hidden++;
}
このように、static
は「どこから見えるか(スコープ/リンケージ)」と「どれくらい生きるか(記憶期間)」の両方に関わる重要なキーワードです。
まとめ
本記事では、C言語の変数について、宣言・初期化・代入の基本、命名と予約語、const
による保護、メモリ上での仕組み、リテラルとの違い、基本型とサイズ/範囲、暗黙/明示の型変換、そしてスコープと記憶期間までを一通り解説しました。
Cでは型が挙動を左右し、初期化やスコープの理解が安全で読みやすいコードの鍵になります。
まずは小さなプログラムで、ここで紹介したパターンを実際に動かし、出力を観察することから始めてください。
挙動を自分の目で確かめることが、C言語の確かな理解につながります。