C言語の sizeof 演算子を徹底解説!初心者でも安心、メモリの大きさを知る旅へ
はじめに:C言語とメモリ、そして「大きさ」の重要性
C言語の世界へようこそ! C言語は、コンピュータのハードウェアに非常に近いレベルで動作する、パワフルな言語です。メモリを直接的に扱える自由度がある反面、メモリ管理をしっかり理解しておくことが非常に重要になります。
さて、コンピュータがプログラムを実行するとき、データはすべてメモリという場所に格納されます。皆さんが普段目にする文字や数字、画像や音声といったものも、コンピュータの内部ではすべて「ビット」や「バイト」といった単位で表現され、メモリ上のどこかに配置されています。
データには種類がありますね。例えば、小さな整数、大きな整数、小数点を含む数字、一文字などです。これらの「種類」をC言語では「データ型」と呼びます。そして、データ型によって、メモリ上で占める「大きさ」が異なります。
この「大きさ」を知ることは、C言語プログラミングにおいて非常に大切です。なぜなら、メモリを効率的に使うため、配列を正しく扱うため、ポインタを理解するため、さらには高度なデータ構造を扱うためにも、データの大きさを正確に把握する必要があるからです。
では、どうすればC言語でデータ型や変数がメモリ上でどれくらいの大きさを占めているのかを知ることができるのでしょうか?
ここで登場するのが、今回主役となる sizeof
演算子です!
sizeof
演算子は、C言語において、指定したデータ型や変数がメモリ上で何バイトの領域を使用するかを教えてくれる、非常に便利な機能です。メモリの大きさを「測る」ことができる、定規のようなものだと考えてください。
この記事では、C言語を学び始めたばかりの皆さんに向けて、この sizeof
演算子の使い方を、基本的なことから応用的なことまで、豊富な例を交えながら徹底的に解説していきます。
この記事を読めば、以下のことが理解できるようになります。
- なぜメモリの大きさを知る必要があるのか
sizeof
演算子の基本的な使い方- 様々なデータ型(基本型、配列、ポインタ、構造体など)に
sizeof
を適用した場合の結果 sizeof
を使う上での注意点や落とし穴- 実際のプログラミングで
sizeof
がどのように役立つか
さあ、C言語のメモリの大きさを知る旅に出かけましょう!
第1章:メモリの基本と sizeof の役割
C言語において sizeof
を理解するためには、まずコンピュータのメモリがどのように扱われているかを少しだけ知っておくと役に立ちます。
1.1 コンピュータのメモリの仕組み(超入門)
コンピュータのメモリは、たくさんの小さな箱がずらっと並んだ倉庫のようなものだと考えてください。それぞれの箱には「住所」がついていて、その住所を「メモリアドレス」と呼びます。
これらの箱の最小単位は「バイト(byte)」です。1バイトは通常8つの「ビット(bit)」から構成されます。ビットは0か1のどちらかの状態を表すことができます。8つのビットが集まると、1バイトとして様々な情報を表現できるようになります。例えば、1バイトでアルファベットの一文字を表したり、0から255までの整数を表したりできます。
データ型によってメモリ上で占めるバイト数が異なります。
- 小さな整数 (
char
) は1バイトで済むことが多いです。 - 通常の整数 (
int
) は4バイトを使うことが多いです。 - 小数点を含む数 (
double
) は8バイトを使うことが多いです。
このように、データ型によって必要な「箱の数」(バイト数)が変わってくるのです。
1.2 なぜデータ型の大きさを知る必要があるのか?
データ型の大きさを知ることは、C言語プログラミングにおいて様々な場面で重要になります。いくつか例を見てみましょう。
- メモリ効率: 限られたメモリを効率的に使うためには、各データがどれくらいのメモリを消費するのかを把握しておく必要があります。特に組み込みシステムなど、メモリが非常に少ない環境では重要です。
- 配列の操作: 配列は同じ型のデータが連続してメモリに並んだものです。配列全体のサイズや、配列の中にいくつのデータが入っているかを知るためには、データ一つの大きさを知る必要があります。
- ポインタ演算: ポインタはメモリのアドレスを保持する変数です。ポインタを使った計算(例えば、配列の次の要素のアドレスを計算するなど)では、指しているデータ型のサイズを基準に行われます。
- 動的なメモリ確保: プログラムの実行中に必要な分のメモリを確保する際には、「〇バイト分ちょうだい」という形で要求します。この〇バイトの計算にデータ型のサイズが必要です。
これらの操作を正確に行うためには、「このデータ型は一体何バイトなんだろう?」という疑問に答えられる必要があります。その答えを教えてくれるのが、sizeof
演算子なのです。
1.3 sizeof が教えてくれること
sizeof
演算子は、オペランド(対象)がメモリ上で占めるバイト数を返します。
オペランドには、以下のいずれかを指定できます。
- データ型の名前:
int
,char
,float
,struct MyStruct
など - 変数名:
myVariable
など - 式: ただし、式の「値」ではなく、式の「結果の型」に基づいてサイズが計算されます。(例:
sizeof(1 + 2)
はint
型のサイズを返します)
sizeof
が返す値は、符号なし整数型(通常 size_t
型)です。この size_t
型は、システム上のあらゆるオブジェクトのサイズを表すことができる十分な大きさを持つように定義されています。<stddef.h>
ヘッダーファイルをインクルードすると size_t
型を使用できますが、多くの場合は printf
で %zu
フォーマット指定子を使うことで size_t
型の値を表示できます。(古いコンパイラでは %u
や %lu
を使うこともありますが、現代的なC言語では %zu
が推奨されます)。
さて、基本的な役割が分かったところで、次章では実際の使い方を見ていきましょう。
第2章:sizeof 演算子の基本的な使い方と例
sizeof
演算子の使い方は非常にシンプルです。対象が「型」なのか「変数/式」なのかによって、記述方法が少しだけ異なります。
2.1 構文
sizeof
演算子の構文は以下の2パターンです。
-
データ型を指定する場合:
c
sizeof(データ型名)
この場合、必ず括弧()
が必要です。 -
変数名または式を指定する場合:
c
sizeof 変数名
c
sizeof 式
この場合、変数名や式が単独であれば括弧()
は省略できます。ただし、混乱を避けるため、常に括弧()
を付けてsizeof(変数名)
やsizeof(式)
のように書くスタイルも一般的です。特に初心者の方は、常に括弧を付ける習慣をつけておくと間違いが少ないでしょう。
2.2 基本的なデータ型のサイズを測る
まずは、C言語の基本的なデータ型に sizeof
を適用してみましょう。
例 2.1: 基本データ型のサイズを知る
“`c
include // printf関数を使うために必要
include // CHAR_BIT などのマクロを使うために必要 (参考)
int main() {
// データ型名を指定する場合 (括弧必須)
printf(“char 型のサイズ: %zu バイト\n”, sizeof(char));
printf(“short int 型のサイズ: %zu バイト\n”, sizeof(short int));
printf(“int 型のサイズ: %zu バイト\n”, sizeof(int));
printf(“long int 型のサイズ: %zu バイト\n”, sizeof(long int));
printf(“long long int 型のサイズ: %zu バイト\n”, sizeof(long long int));
printf(“float 型のサイズ: %zu バイト\n”, sizeof(float));
printf(“double 型のサイズ: %zu バイト\n”, sizeof(double));
printf(“long double 型のサイズ: %zu バイト\n”, sizeof(long double));
printf("---\n");
// 変数名を指定する場合 (括弧省略可能だが付けてもOK)
int num = 10;
char initial = 'A';
double pi = 3.14;
printf("変数 num (int型) のサイズ: %zu バイト\n", sizeof(num));
printf("変数 initial (char型) のサイズ: %zu バイト\n", sizeof(initial));
printf("変数 pi (double型) のサイズ: %zu バイト\n", sizeof(pi));
// 式を指定する場合 (式の評価は行われず、型に基づいてサイズを計算)
printf("式 (1 + 2) (int型) のサイズ: %zu バイト\n", sizeof(1 + 2));
printf("式 (3.0 / 4.0) (double型) のサイズ: %zu バイト\n", sizeof(3.0 / 4.0));
// 1バイトは何ビットか?も知っておくと良いでしょう (通常8ビット)
// limits.h に定義されている CHAR_BIT を使います
printf("---\n");
printf("1 バイトは %d ビットです。\n", CHAR_BIT);
return 0;
}
“`
実行結果の例:
“`
char 型のサイズ: 1 バイト
short int 型のサイズ: 2 バイト
int 型のサイズ: 4 バイト
long int 型のサイズ: 8 バイト
long long int 型のサイズ: 8 バイト
float 型のサイズ: 4 バイト
double 型のサイズ: 8 バイト
long double 型のサイズ: 16 バイト
変数 num (int型) のサイズ: 4 バイト
変数 initial (char型) のサイズ: 1 バイト
変数 pi (double型) のサイズ: 8 バイト
式 (1 + 2) (int型) のサイズ: 4 バイト
式 (3.0 / 4.0) (double型) のサイズ: 8 バイト
1 バイトは 8 ビットです。
“`
解説:
printf
関数でsizeof
の結果を表示しています。%zu
はsize_t
型を表示するためのフォーマット指定子です。sizeof(char)
は、C言語の規格で 常に1バイト と定められています。これは、sizeof
演算子で得られる結果の基準となります。int
,short int
,long int
,long long int
といった整数型や、float
,double
,long double
といった浮動小数点型のサイズは、実行するコンピュータのシステムやコンパイラによって異なる場合があります。 上記の例はあくまで一般的な環境(例えば64bitのLinuxやWindows)での一例です。例えば、古いシステムではint
が2バイトだったり、long int
が4バイトだったりすることもあります。sizeof(num)
のように変数名を指定した場合、sizeof
はその変数の「型」に基づいてサイズを計算します。変数num
はint
型なので、sizeof(num)
はsizeof(int)
と同じ結果になります。sizeof(1 + 2)
のように式を指定した場合、実際に計算が行われるわけではありません。sizeof
は、その式の結果がどのデータ型になるか(この場合は整数リテラルの足し算なのでint
型)を判断し、その型のサイズを返します。同様に、sizeof(3.0 / 4.0)
は浮動小数点数の割り算なので結果はdouble
型となり、double
型のサイズが返されます。CHAR_BIT
は<limits.h>
で定義されており、1バイトが何ビットであるかを示します。ほとんどのシステムでは8です。sizeof
が返す単位は常にバイトなので、ビット単位のサイズを知りたい場合はsizeof(型) * CHAR_BIT
で計算できます。
このように、sizeof
を使うことで、様々なデータ型がメモリ上でどれくらいの大きさを占めているかを簡単に調べることができます。
2.3 sizeof
の結果はコンパイル時に決まる「定数」?
ほとんどの場合、sizeof
演算子はコンパイル時にその結果が計算されます。つまり、プログラムが実行される前に、コンパイラが各 sizeof
が返す値を確定させています。そのため、sizeof
の結果は基本的には「定数」として扱われます。
例えば、配列のサイズを定義する際に sizeof
を使うことも可能です(ただし、C99以降の機能やコンパイラに依存する場合もあります)。
“`c
include
int main() {
int size_of_int = sizeof(int); // コンパイル時に確定
int numbers[size_of_int * 10]; // size_of_int は定数として扱える場合がある
printf("numbers配列のサイズ: %zu バイト\n", sizeof(numbers));
return 0;
}
“`
ただし、後述する「可変長配列(VLA)」のように、C99以降で導入された一部の機能では、sizeof
の結果が実行時に決定される場合があります。しかし、基本的なデータ型や固定サイズの配列、構造体などに対する sizeof
は、コンパイル時定数として扱われると理解しておいてください。
第3章:様々なデータ型と sizeof
基本データ型だけでなく、C言語には配列、ポインタ、構造体、共用体といった、より複雑なデータ型があります。これらの型に対して sizeof
を使うと、どのような結果になるのでしょうか? ここからが sizeof
の理解を深める上で非常に重要な部分です。
3.1 配列と sizeof
配列は、同じ型のデータがメモリ上で連続して並んだものです。配列全体が占めるメモリ領域のサイズは、「要素一つあたりのサイズ」×「要素の数」 で計算できます。
sizeof
演算子を配列に適用すると、その配列がメモリ上で占める全体のバイト数が返されます。
例 3.1: 配列のサイズ
“`c
include
int main() {
int scores[5]; // 要素数5のint型配列
double prices[10]; // 要素数10のdouble型配列
char name[] = “C Language”; // 文字列リテラルで初期化されたchar型配列
// 配列全体のサイズ
printf("scores 配列全体のサイズ: %zu バイト\n", sizeof(scores));
printf("prices 配列全体のサイズ: %zu バイト\n", sizeof(prices));
printf("name 配列全体のサイズ: %zu バイト\n", sizeof(name)); // null終端文字('\0')も含まれることに注意
// 配列の要素一つのサイズ
printf("scores 配列の要素一つのサイズ: %zu バイト\n", sizeof(scores[0])); // scores[0] はint型
printf("prices 配列の要素一つのサイズ: %zu バイト\n", sizeof(prices[0])); // prices[0] はdouble型
printf("name 配列の要素一つのサイズ: %zu バイト\n", sizeof(name[0])); // name[0] はchar型
return 0;
}
“`
実行結果の例: (int=4バイト, double=8バイト, char=1バイトのシステムの場合)
scores 配列全体のサイズ: 20 バイト (5要素 * 4バイト/要素)
prices 配列全体のサイズ: 80 バイト (10要素 * 8バイト/要素)
name 配列全体のサイズ: 11 バイト ("C Language" は10文字 + null終端文字('\0') 1文字 = 11文字 * 1バイト/文字)
scores 配列の要素一つのサイズ: 4 バイト
prices 配列の要素一つのサイズ: 8 バイト
name 配列の要素一つのサイズ: 1 バイト
解説:
sizeof(scores)
は配列scores
全体のサイズを返します。scores
は5つのint
型要素を持つので、その合計サイズは5 * sizeof(int)
となります。sizeof(scores[0])
のように配列の特定の要素にsizeof
を適用すると、その要素の型 (int
型) のサイズが返されます。配列の要素はすべて同じ型なので、sizeof(scores[0])
はsizeof(scores[1])
やsizeof(int)
と同じ結果になります。- 文字列リテラルで初期化された
char
型配列name
の場合、配列のサイズはその文字列に含まれる文字数に、終端を示す\0
(null終端文字) 1文字分を加えたサイズになります。”C Language” は10文字なので、name
のサイズは10 + 1 = 11
バイトとなります。
3.1.1 配列の要素数を計算する
配列の要素数を計算するために sizeof
を使うのは、C言語で非常によく行われるテクニックです。
配列全体のサイズを、要素一つあたりのサイズで割れば、要素の数が得られます。
要素数 = sizeof(配列名)
/ sizeof(配列名[0])
例 3.2: 配列の要素数を計算
“`c
include
int main() {
int data[] = {10, 20, 30, 40, 50, 60}; // 要素数を指定しない初期化 (コンパイラが数えてくれる)
int num_elements;
// data配列全体のサイズを取得
size_t total_size = sizeof(data);
// data配列の要素一つのサイズを取得
size_t element_size = sizeof(data[0]);
// 要素数を計算
num_elements = total_size / element_size;
printf("data 配列全体のサイズ: %zu バイト\n", total_size);
printf("data 配列の要素一つのサイズ: %zu バイト\n", element_size);
printf("data 配列の要素数: %d\n", num_elements);
return 0;
}
“`
実行結果の例: (int=4バイトのシステムの場合)
data 配列全体のサイズ: 24 バイト (6要素 * 4バイト/要素)
data 配列の要素一つのサイズ: 4 バイト
data 配列の要素数: 6
解説:
このテクニックは、配列を初期化する際に要素数を明示的に指定しなかった場合や、後から配列の要素数を調べたい場合に非常に役立ちます。特に、配列を他の関数に渡す際に問題となる点を理解する上でも、この sizeof
を使った要素数計算の考え方は重要になります(後述の注意点を参照)。
注意: この sizeof(配列名) / sizeof(配列名[0])
という方法は、配列が「配列」として扱われている場合のみ有効です。配列がポインタとして扱われている場合には使えません。これが最も重要な落とし穴の一つです。(詳細は第5章で解説します)
3.2 ポインタと sizeof
ポインタは、メモリ上の「アドレス」を格納するための変数です。ポインタ変数自身もメモリ上に存在し、サイズを持っています。
sizeof
演算子をポインタ変数に適用すると、そのポインタ変数がメモリ上で占めるバイト数が返されます。これは、ポインタが「どの型」を指しているかには関係なく、アドレスを格納するために必要な固定のサイズになります。
ポインタのサイズは、システムがメモリアドレスを管理するために使用するビット数によって決まります。
- 32bitシステムでは、アドレスは32bit(4バイト)で表現されることが多いため、ポインタのサイズも4バイトであることが多いです。
- 64bitシステムでは、アドレスは64bit(8バイト)で表現されることが多いため、ポインタのサイズも8バイトであることが多いです。
例 3.3: ポインタ変数のサイズ
“`c
include
int main() {
int ptr_int; // int型を指すポインタ
char ptr_char; // char型を指すポインタ
double ptr_double; // double型を指すポインタ
void ptr_void; // 型を指定しない汎用ポインタ (void*)
// ポインタ変数のサイズ
printf("int* 型のサイズ: %zu バイト\n", sizeof(ptr_int));
printf("char* 型のサイズ: %zu バイト\n", sizeof(ptr_char));
printf("double* 型のサイズ: %zu バイト\n", sizeof(ptr_double));
printf("void* 型のサイズ: %zu バイト\n", sizeof(ptr_void));
// 型名でポインタのサイズを知ることもできます
printf("int* 型のサイズ (型名): %zu バイト\n", sizeof(int*));
printf("char* 型のサイズ (型名): %zu バイト\n", sizeof(char*));
return 0;
}
“`
実行結果の例: (64bitシステムの場合)
int* 型のサイズ: 8 バイト
char* 型のサイズ: 8 バイト
double* 型のサイズ: 8 バイト
void* 型のサイズ: 8 バイト
int* 型のサイズ (型名): 8 バイト
char* 型のサイズ (型名): 8 バイト
解説:
ご覧のように、int*
, char*
, double*
, void*
と、指している型が異なっていても、ポインタ変数自体のサイズは同じになります。これは、ポインタが格納しているのはあくまで「メモリアドレス」であり、そのアドレスの表現に必要なサイズは、指すデータの型には依存しないからです。
3.2.1 ポインタが指す先のサイズ
ポインタが指す先のデータのサイズを知りたい場合は、間接参照演算子 *
を使います。
sizeof(*ポインタ変数)
は、そのポインタが現在指している「データ」の型に基づいてサイズを計算します。
例 3.4: ポインタが指す先のサイズ
“`c
include
int main() {
int num = 100;
char character = ‘Z’;
double value = 99.9;
int *ptr_num = # // numのアドレスを指すポインタ
char *ptr_character = &character; // characterのアドレスを指すポインタ
double *ptr_value = &value; // valueのアドレスを指すポインタ
// ポインタ変数自身のサイズ (システム依存、ここでは8バイトと仮定)
printf("ptr_num のサイズ: %zu バイト\n", sizeof(ptr_num));
printf("ptr_character のサイズ: %zu バイト\n", sizeof(ptr_character));
printf("ptr_value のサイズ: %zu バイト\n", sizeof(ptr_value));
printf("---\n");
// ポインタが指す先のデータのサイズ
printf("*ptr_num のサイズ: %zu バイト (ptr_numが指すint型のサイズ)\n", sizeof(*ptr_num));
printf("*ptr_character のサイズ: %zu バイト (ptr_characterが指すchar型のサイズ)\n", sizeof(*ptr_character));
printf("*ptr_value のサイズ: %zu バイト (ptr_valueが指すdouble型のサイズ)\n", sizeof(*ptr_value));
return 0;
}
“`
実行結果の例: (64bitシステム、int=4, char=1, double=8バイトの場合)
“`
ptr_num のサイズ: 8 バイト
ptr_character のサイズ: 8 バイト
ptr_value のサイズ: 8 バイト
ptr_num のサイズ: 4 バイト (ptr_numが指すint型のサイズ)
ptr_character のサイズ: 1 バイト (ptr_characterが指すchar型のサイズ)
*ptr_value のサイズ: 8 バイト (ptr_valueが指すdouble型のサイズ)
“`
解説:
sizeof(ptr_num)
などは、ポインタ変数自身のサイズ(アドレスを格納する領域のサイズ)を返します。sizeof(*ptr_num)
は、ポインタptr_num
が指している場所にある「データ」(この例ではnum
というint
型変数)のサイズを返します。*ptr_num
はint
型の「値」として扱われるため、sizeof(*ptr_num)
はsizeof(int)
と同じになります。
この違いは非常に重要です。sizeof(ポインタ変数)
はポインタ自身のサイズ、sizeof(*ポインタ変数)
はポインタが指すデータのサイズです。ポインタ演算(例えば ptr++
)では、ポインタ自身のサイズではなく、ポインタが指すデータのサイズ分だけアドレスが進む、という規則とも関連しています。
3.3 構造体 (struct) と sizeof
構造体 (struct
) は、異なるデータ型の変数をひとまとめにして扱うことができるユーザー定義型です。
sizeof
演算子を構造体に適用すると、その構造体の変数がメモリ上で占める全体のバイト数が返されます。
ここで一つ重要な概念が出てきます。それは 「パディング (Padding)」 です。
3.3.1 構造体のパディング (Padding)
コンピュータのプロセッサは、メモリからデータを読み書きする際に、特定のメモリアドレス(例えば、4バイト境界や8バイト境界など)に配置されているデータを効率的に扱うことができます。これを「アライメント(Alignment)」と呼びます。
C言語の処理系(コンパイラなど)は、このアライメント要求を満たすために、構造体のメンバの間や、構造体の最後に「詰め物」として無駄な領域を挿入することがあります。これがパディングです。
パディングが行われるため、構造体のサイズは、そのメンバそれぞれのサイズの合計よりも大きくなることがあります。
構造体のサイズ >= 各メンバのサイズの合計
例 3.5: 構造体のサイズとパディング
“`c
include
// メンバを宣言する順番によってサイズが変わる可能性がある例
struct Data1 {
char c; // 1バイト
int i; // 4バイト
short s; // 2バイト
}; // メンバの合計: 1 + 4 + 2 = 7 バイト
struct Data2 {
char c; // 1バイト
short s; // 2バイト
int i; // 4バイト
}; // メンバの合計: 1 + 2 + 4 = 7 バイト
// 構造体の中に構造体を持つ場合
struct Inner {
char c1; // 1バイト
char c2; // 1バイト
}; // メンバの合計: 2 バイト (パディングされるか?)
struct Outer {
int i; // 4バイト
struct Inner in; // Inner構造体
char c; // 1バイト
}; // メンバの合計: 4 + sizeof(struct Inner) + 1 バイト
int main() {
printf(“sizeof(struct Data1): %zu バイト\n”, sizeof(struct Data1));
printf(“sizeof(struct Data2): %zu バイト\n”, sizeof(struct Data2));
printf(“sizeof(struct Inner): %zu バイト\n”, sizeof(struct Inner));
printf(“sizeof(struct Outer): %zu バイト\n”, sizeof(struct Outer));
// 各メンバのサイズを確認 (参考)
printf("---\n");
printf("sizeof(char): %zu\n", sizeof(char));
printf("sizeof(int): %zu\n", sizeof(int));
printf("sizeof(short): %zu\n", sizeof(short));
return 0;
}
“`
実行結果の例: (int=4バイト, short=2バイト, char=1バイトのシステムの場合)
“`
sizeof(struct Data1): 12 バイト // メンバ合計7バイトだが、パディングで12バイトになる可能性
sizeof(struct Data2): 8 バイト // メンバ合計7バイトだが、パディングで8バイトになる可能性
sizeof(struct Inner): 2 バイト // メンバ合計2バイト、パディングなしの場合
sizeof(struct Outer): 12 バイト // メンバ合計 4 + sizeof(Inner) + 1 にパディングが加わる可能性
sizeof(char): 1
sizeof(int): 4
sizeof(short): 2
“`
解説:
struct Data1
とstruct Data2
は、同じメンバ(char
,int
,short
)を持っていますが、宣言する順番が異なります。メンバ自体のサイズの合計は1 + 4 + 2 = 7
バイトですが、多くのシステムでは構造体のサイズがメンバの合計サイズよりも大きくなります。これはパディングによるものです。- 例えば、
struct Data1
ではchar c
(1バイト) の後にint i
(4バイト) が来ますが、int
は4バイト境界に配置されることが多いため、c
の後ろに3バイトのパディングが挿入され、i
が4バイト目のアドレスから始まるように調整される可能性があります (c[1] + padding[3] + i[4] + s[2] + padding[2] = 12
のような配置)。 - 一方、
struct Data2
ではchar c
(1バイト) の後にshort s
(2バイト) が来ます。short
は2バイト境界に配置されることが多いので、c
の後ろに1バイトのパディングが挿入され、s
が2バイト目のアドレスから始まるように調整される可能性があります (c[1] + padding[1] + s[2] + i[4] = 8
のような配置)。そして、構造体全体のサイズも、最大のメンバのアライメント要求や構造体の終端へのパディングによって調整されます。
- 例えば、
struct Inner
のように、メンバの合計サイズが比較的小さく、アライメント要求が厳しくない場合は、パディングが少ないか、全く発生しないこともあります。この例ではchar
が2つなので、2バイトとなる可能性が高いです。struct Outer
のように、構造体が別の構造体をメンバとして持つ場合も、同様にパディングが考慮されます。Inner
構造体のサイズとそのアライメント、そしてOuter
自身のメンバi
やc
との間、さらに構造体全体の最後にパディングが挿入される可能性があります。
パディングのルールは、コンパイラやCPUアーキテクチャによって異なります。したがって、構造体の実際のサイズは、sizeof
を使って実行環境で確認することが重要です。構造体のメンバにアクセスする際には、ポインタ演算などで安易にメンバのアドレスを計算せず、メンバ名を使ってアクセスするべきなのは、このパディングがあるためです。
3.3.2 構造体の特定のメンバのサイズ
構造体変数や構造体型名に sizeof
を適用すると、構造体全体のサイズが分かりますが、特定のメンバのサイズを知りたい場合は、そのメンバの型や変数を使います。
例 3.6: 構造体メンバのサイズ
“`c
include
struct Point {
int x;
int y;
};
struct Circle {
struct Point center; // 構造体をメンバとして持つ
double radius;
};
int main() {
struct Circle c;
// 構造体全体のサイズ
printf("sizeof(struct Point): %zu バイト\n", sizeof(struct Point));
printf("sizeof(struct Circle): %zu バイト\n", sizeof(struct Circle));
// 構造体の特定のメンバのサイズ (メンバの型や、変数.メンバ名を使う)
printf("---\n");
printf("sizeof(c.center): %zu バイト (centerメンバのサイズ)\n", sizeof(c.center)); // centerメンバ (struct Point型)
printf("sizeof(c.radius): %zu バイト (radiusメンバのサイズ)\n", sizeof(c.radius)); // radiusメンバ (double型)
printf("sizeof(((struct Point*)0)->x): %zu バイト (xメンバのサイズ、トリッキーな方法)\n", sizeof(((struct Point*)0)->x)); // Point構造体のxメンバ (int型)
return 0;
}
“`
実行結果の例: (int=4バイト, double=8バイトのシステムの場合)
“`
sizeof(struct Point): 8 バイト // int 2つで8バイト (パディングなしの場合)
sizeof(struct Circle): 16 バイト // Point(8) + double(8) = 16 バイト (パディングなしの場合)
sizeof(c.center): 8 バイト (centerメンバのサイズ)
sizeof(c.radius): 8 バイト (radiusメンバのサイズ)
sizeof(((struct Point*)0)->x): 4 バイト (xメンバのサイズ、トリッキーな方法)
“`
解説:
sizeof(c.center)
は、構造体変数c
のcenter
メンバにsizeof
を適用しています。center
メンバはstruct Point
型なので、sizeof(c.center)
はsizeof(struct Point)
と同じ結果になります。sizeof(c.radius)
は、radius
メンバにsizeof
を適用しています。radius
メンバはdouble
型なので、sizeof(c.radius)
はsizeof(double)
と同じ結果になります。sizeof(((struct Point*)0)->x)
という記述は少し高度ですが、これも特定のメンバのサイズを知るためによく使われるテクニックです。これは「アドレス0にあるstruct Point
型の構造体があるとしたら、そのx
メンバの型は何だろう?」という考え方で、コンパイル時にx
メンバの型(この場合はint
)を特定し、その型のサイズを返すものです。実際にアドレス0にアクセスするわけではありません。
3.4 共用体 (union) と sizeof
共用体 (union
) は、複数のメンバが同じメモリ領域を共有する特殊なデータ型です。共用体のサイズは、そのメンバの中で最も大きいメンバのサイズと同じになります。これは、共用体がどのメンバを保持している場合でも、そのメンバを格納できるだけの十分な領域を確保する必要があるためです。
共用体のサイズ = Max(各メンバのサイズ)
パディングも考慮されますが、基本的な考え方は「最大のメンバのサイズ」です。共用体全体のサイズは、最も大きなメンバのアライメント要求を満たすように調整されることがあります。
例 3.7: 共用体のサイズ
“`c
include
union Value {
int i; // 例えば4バイト
float f; // 例えば4バイト
double d; // 例えば8バイト
char c; // 1バイト
}; // この共用体で最も大きいのは double (8バイト)
int main() {
printf(“sizeof(union Value): %zu バイト\n”, sizeof(union Value));
// 各メンバのサイズを確認 (参考)
printf("---\n");
printf("sizeof(int): %zu\n", sizeof(int));
printf("sizeof(float): %zu\n", sizeof(float));
printf("sizeof(double): %zu\n", sizeof(double));
printf("sizeof(char): %zu\n", sizeof(char));
return 0;
}
“`
実行結果の例: (int=4, float=4, double=8, char=1バイトのシステムの場合)
“`
sizeof(union Value): 8 バイト // doubleが最も大きいメンバなので8バイト (アライメント考慮でこれ以上になる可能性もゼロではないが、通常は最大メンバサイズに揃う)
sizeof(int): 4
sizeof(float): 4
sizeof(double): 8
sizeof(char): 1
“`
解説:
共用体 union Value
は、int
(4バイト), float
(4バイト), double
(8バイト), char
(1バイト) のメンバを持っています。これらのメンバの中で最もサイズが大きいのは double
の8バイトです。したがって、sizeof(union Value)
は8バイトになります。これにより、共用体変数はこの8バイトの領域を使って、どのメンバでも格納できるようになります。
3.5 列挙型 (enum) と sizeof
列挙型 (enum
) は、整数の定数に名前を付けることで、コードの可読性を高めるための型です。
sizeof
演算子を列挙型に適用すると、その列挙型を格納するために必要な整数のサイズのバイト数が返されます。C言語の規格では、列挙型のサイズは処理系定義(実装によって決まる)とされていますが、多くの処理系では int
型と同じサイズになります。
例 3.8: 列挙型のサイズ
“`c
include
enum Color {
RED, // 0
GREEN, // 1
BLUE // 2
};
enum Status {
SUCCESS = 0,
FAILURE = 1,
PENDING = 2 // 値を指定してもサイズには影響しない
};
int main() {
enum Color my_color = RED;
enum Status current_status = PENDING;
printf("sizeof(enum Color): %zu バイト\n", sizeof(enum Color));
printf("sizeof(enum Status): %zu バイト\n", sizeof(enum Status));
printf("sizeof(my_color): %zu バイト\n", sizeof(my_color)); // 変数名を指定
printf("sizeof(current_status): %zu バイト\n", sizeof(current_status)); // 変数名を指定
// 参考: int型のサイズ
printf("---\n");
printf("sizeof(int): %zu バイト\n", sizeof(int));
return 0;
}
“`
実行結果の例: (int=4バイトのシステムの場合)
“`
sizeof(enum Color): 4 バイト
sizeof(enum Status): 4 バイト
sizeof(my_color): 4 バイト
sizeof(current_status): 4 バイト
sizeof(int): 4 バイト
“`
解説:
この例からも分かるように、列挙型 enum Color
および enum Status
のサイズは、どちらも int
型のサイズと同じ4バイトになっています。これは、列挙子の値が内部的に整数として扱われるためです。
第4章:sizeof を使う上での注意点と落とし穴
sizeof
演算子は便利ですが、いくつか注意すべき点や、初心者が陥りやすい落とし穴があります。これらを理解しておくと、より正確に sizeof
を使いこなせるようになります。
4.1 sizeof
はコンパイル時定数?(再確認と例外)
先ほど、sizeof
の結果はほとんどの場合コンパイル時に決まる定数であると説明しました。これは非常に重要な性質です。例えば、以下のような使い方ができます。
c
int arr[sizeof(int) * 10]; // OK (sizeof(int) はコンパイル時定数なので、配列のサイズを定数で指定できる)
const int array_size = sizeof(double) * 5; // OK (sizeof(double) はコンパイル時定数)
しかし、C99規格以降で導入された「可変長配列 (Variable Length Array: VLA)」に対して sizeof
を適用した場合、その結果は実行時に決定されることがあります。
“`c
include
int main() {
int n = 5;
int vla[n]; // 可変長配列 (サイズ n は実行時に決定)
// VLAに対するsizeofは、実行時に評価される可能性がある
printf("VLA vla のサイズ: %zu バイト\n", sizeof(vla));
return 0;
}
“`
この場合、変数 n
の値がプログラムの実行中に決まるため、vla
のサイズも実行時まで分かりません。したがって、sizeof(vla)
の評価も実行時に行われます。ただし、VLAに対する sizeof
の結果は定数式ではないため、静的な(コンパイル時にサイズが決まる)配列のサイズ指定や、列挙子の値として使うことはできません。
初心者の方は、まず「sizeof
は基本的にコンパイル時定数」と理解しておき、VLAのような特別なケースでは実行時評価になることもある、と補足的に覚えておくと良いでしょう。
4.2 関数に sizeof
は使えない
sizeof
演算子は、データ型や変数、式に適用できますが、関数名そのものに適用することはできません。
“`c
include
void greet() {
printf(“Hello!\n”);
}
int main() {
// printf(“sizeof(greet): %zu バイト\n”, sizeof(greet)); // これはコンパイルエラー!
return 0;
}
“`
関数は「データ」としてメモリ上に存在するものではなく、実行される「処理」のまとまりだからです。関数のサイズ(コードが占めるメモリ領域)を知る方法もありますが、それは sizeof
の役割ではありません。
4.3 不完全型 (Incomplete Types) に注意
C言語には「不完全型」と呼ばれる概念があります。これは、型が宣言されているけれど、その型のサイズがまだ確定していない状態の型です。不完全型に対して sizeof
を適用しようとすると、コンパイルエラーになります。
代表的な不完全型は以下の通りです。
- 要素数が未定の配列:
int arr[];
のように、サイズを指定せずに宣言された配列(ただし、定義時に初期化子があればサイズは確定します)。 - 宣言のみで定義されていない構造体や共用体:
struct MyStruct;
のように宣言されたが、そのメンバがまだ定義されていない場合。ただし、このような不完全型へのポインタは、ポインタのサイズが一定なのでsizeof
を適用できます (sizeof(struct MyStruct*)
はOK)。 void
型:void
は「型なし」を意味するため、サイズがありません。sizeof(void)
はエラーです。
例 4.1: 不完全型と sizeof
“`c
include
// struct UnknownStruct; // 不完全型 (宣言のみ)
int main() {
// int incomplete_arr[]; // 要素数が未定の配列 (不完全型)
// printf("sizeof(incomplete_arr): %zu\n", sizeof(incomplete_arr)); // エラー!
// struct UnknownStruct* ptr; // 不完全型へのポインタはOK
// printf("sizeof(ptr): %zu\n", sizeof(ptr)); // ポインタのサイズは取得できる
// printf("sizeof(void): %zu\n", sizeof(void)); // エラー!
// サイズが確定している配列はOK
int complete_arr[5];
printf("sizeof(complete_arr): %zu バイト\n", sizeof(complete_arr));
return 0;
}
“`
この例でコメントアウトされている sizeof
はコンパイルエラーになります。sizeof
は、対象となる型のサイズをコンパイル時に決定する必要があるため、サイズが不明な不完全型には使えません。
4.4 配列を関数に渡した場合の sizeof
の落とし穴(最も重要!)
C言語において、配列を関数に引数として渡す際、配列は配列そのものとしてではなく、その配列の先頭要素へのポインタとして渡されます。これは「配列のポインタへの衰退 (Array Decay to Pointer)」と呼ばれます。
この性質のため、関数内で配列の引数に対して sizeof
を適用しても、それは配列全体のサイズではなく、ポインタ変数のサイズを返してしまいます。
これは、配列の要素数を sizeof(配列名) / sizeof(配列名[0])
で計算するテクニックが、関数内で通用しないことを意味します。
例 4.2: 関数に配列を渡した場合の sizeof
“`c
include
// 配列を引数に取る関数 (実際にはポインタとして渡される)
void print_array_size(int arr[]) {
// arr は実際には int* 型のポインタとして扱われる
printf(“関数内での sizeof(arr): %zu バイト\n”, sizeof(arr)); // ポインタのサイズが返される!
// printf(“関数内での要素数 (sizeofを使った誤った方法): %zu\n”, sizeof(arr) / sizeof(arr[0])); // ポインタサイズ / intサイズ となり、誤った要素数になる
}
// 配列のサイズも一緒に渡す関数
void print_array_info(int arr[], size_t arr_size) {
printf(“関数内での sizeof(arr) (ポインタ): %zu バイト\n”, sizeof(arr)); // やはりポインタのサイズ
printf(“関数に渡された配列のサイズ: %zu バイト\n”, arr_size);
printf(“関数に渡された配列の要素数: %zu\n”, arr_size / sizeof(arr[0]));
}
int main() {
int my_array[] = {10, 20, 30, 40, 50}; // 要素数5のint配列
size_t main_array_size = sizeof(my_array); // main関数内では配列として扱われる
printf("main関数での sizeof(my_array): %zu バイト\n", main_array_size); // 配列全体のサイズ
printf("main関数での要素数: %zu\n", main_array_size / sizeof(my_array[0]));
printf("---\n");
// 関数に配列を渡す
print_array_size(my_array); // 関数内ではポインタとして渡る
printf("---\n");
// 関数に配列とサイズを一緒に渡す
print_array_info(my_array, main_array_size);
return 0;
}
“`
実行結果の例: (int=4バイト、64bitシステムでポインタ=8バイトの場合)
“`
main関数での sizeof(my_array): 20 バイト // 5要素 * 4バイト = 20
main関数での要素数: 5
関数内での sizeof(arr): 8 バイト // ポインタのサイズ
関数内での sizeof(arr) (ポインタ): 8 バイト // ポインタのサイズ
関数に渡された配列のサイズ: 20 バイト // mainから渡された正しいサイズ
関数に渡された配列の要素数: 5 // 正しく計算できる
“`
解説:
main
関数内では、my_array
は実際の配列オブジェクトとして存在するため、sizeof(my_array)
は配列全体のサイズ(20バイト)を正しく返します。要素数も正しく計算できます。- しかし、
print_array_size
関数にmy_array
を渡すと、関数側ではint arr[]
という引数はint* arr
というポインタとして扱われます。したがって、関数内のsizeof(arr)
は、配列全体のサイズではなく、ポインタ変数arr
自体のサイズ(64bitシステムなら8バイト)を返してしまいます。このため、関数内でsizeof(arr) / sizeof(arr[0])
として要素数を計算しようとしても、正しく計算できません。 - 配列を関数に渡してそのサイズ(または要素数)を知りたい場合は、サイズ情報を別の引数として一緒に渡す必要があります。
print_array_info
関数のように、arr_size
という引数で配列の合計バイト数を渡すことで、関数内で要素数を正しく計算できるようになります。
この配列のポインタへの衰退と sizeof
の挙動は、C言語初心者が非常によくつまずく点です。関数に配列を渡す際は、サイズも一緒に渡すという習慣をつけましょう。
第5章:sizeof を使った実践的な例
sizeof
演算子は、実際のプログラミングにおいて様々な場面で活躍します。ここではいくつかの実践的な使用例を紹介します。
5.1 動的メモリ確保 (malloc, calloc)
プログラムの実行中に必要なメモリ領域を確保することを、動的メモリ確保と呼びます。C言語では、malloc
や calloc
といった標準ライブラリ関数を使って動的にメモリを確保します。これらの関数は、確保したいメモリ領域の「バイト数」を引数として受け取ります。このバイト数を計算する際に sizeof
が不可欠です。
malloc(サイズ)
: 指定したサイズのメモリ領域を確保し、その先頭アドレスを返します。calloc(要素数, 要素一つのサイズ)
: 指定した要素数分、かつ各要素のサイズを指定してメモリ領域を確保し、その領域をゼロクリアしてから先頭アドレスを返します。
例 5.1: malloc と sizeof
“`c
include
include // malloc, free関数を使うために必要
int main() {
int num_elements = 10;
int *ptr; // int型のポインタ
// int型の要素10個分のメモリを動的に確保
// 必要なバイト数 = 要素数 * int型1つのサイズ
size_t required_bytes = num_elements * sizeof(int);
ptr = (int*)malloc(required_bytes); // mallocはvoid*を返すので、int*にキャスト
// メモリ確保が成功したか確認
if (ptr == NULL) {
perror("メモリ確保に失敗しました");
return 1; // エラー終了
}
// 確保したメモリ領域を使う (例: 0から9までの値を代入)
printf("%d個のint型 (%zu バイト) のメモリを確保しました。\n", num_elements, required_bytes);
printf("確保された領域の先頭アドレス: %p\n", (void*)ptr); // ポインタのアドレスを表示
for (int i = 0; i < num_elements; i++) {
ptr[i] = i * 10;
// ポインタ演算: ptr + i は、ptrのアドレスから intのサイズ * i バイト先のメモリアドレスを指す
// sizeof(*ptr) は ptrが指す int型のサイズ (4バイト)
// ptr++ は ptrのアドレスを sizeof(*ptr) バイトだけ進める
// printf("アドレス: %p, 値: %d\n", (void*)(ptr + i), ptr[i]); // 各要素のアドレスと値を表示 (オプション)
}
printf("確保したメモリに値を設定しました。\n");
// 値を確認 (例)
printf("最初の要素: %d\n", ptr[0]); // ptr + 0 * sizeof(int) の位置
printf("最後の要素: %d\n", ptr[9]); // ptr + 9 * sizeof(int) の位置
printf("5番目の要素: %d\n", *(ptr + 4)); // *(ptr + 4) == ptr[4]
// 使い終わったメモリは必ず解放する
free(ptr);
printf("確保したメモリを解放しました。\n");
ptr = NULL; // 解放後のポインタはNULLにしておくのが安全
// callocを使った例
int *ptr_calloc;
int num_elements_calloc = 5;
// calloc(要素数, 要素一つのサイズ)
ptr_calloc = (int*)calloc(num_elements_calloc, sizeof(int));
if (ptr_calloc == NULL) {
perror("callocによるメモリ確保に失敗しました");
return 1; // エラー終了
}
printf("\n%d個のint型 (%zu バイト) のメモリをcallocで確保しました。\n", num_elements_calloc, num_elements_calloc * sizeof(int));
printf("確保された領域の先頭アドレス: %p\n", (void*)ptr_calloc); // ポインタのアドレスを表示
// callocは領域をゼロクリアするので、初期値は0になっているはず
printf("callocで確保した領域の最初の要素の初期値: %d\n", ptr_calloc[0]); // 0が表示されるはず
free(ptr_calloc);
printf("callocで確保したメモリを解放しました。\n");
ptr_calloc = NULL;
return 0;
}
“`
解説:
malloc
やcalloc
を使う際には、確保したいデータの合計バイト数を正確に計算して引数に渡す必要があります。num_elements * sizeof(int)
のように、必要な要素数に要素一つのサイズ (sizeof(int)
) を掛けることで、必要な合計バイト数を計算しています。sizeof
を使うことで、int
型がシステム上で何バイトであっても、それに合わせた正確なバイト数を計算できます。もしsizeof
を使わず、例えば常に4と決め打ちしていたら、int
が4バイト以外のシステムでは正しく動作しない可能性があります。ptr = (int*)malloc(...)
のようにキャストしているのは、malloc
が汎用ポインタvoid*
を返すため、代入先のポインタ型に合わせるためです。C++では必須ですが、C言語ではvoid*
から他のポインタ型への代入はキャストなしでも許容されます(しかし、明示的なキャストを行う方が意図が明確になり、バグを防ぎやすい場合もあります)。- 動的に確保したメモリは、使い終わったら必ず
free
関数で解放する必要があります。解放しないとメモリリークの原因となります。
この例は、sizeof
が動的なメモリ管理においていかに基本的で重要であるかを示しています。
5.2 配列の要素数の計算 (再確認)
第3章でも触れましたが、配列の要素数を計算する sizeof(配列名) / sizeof(配列名[0])
は、静的に宣言された配列に対して非常に有用です。
“`c
include
int main() {
int data[] = {10, 20, 30, 40, 50};
int num_elements;
num_elements = sizeof(data) / sizeof(data[0]); // OK: dataは静的に宣言された配列
printf("要素数: %d\n", num_elements);
// 動的に確保したメモリ領域に対するsizeofは使えない!
// int *dynamic_arr = malloc(sizeof(int) * 5);
// printf("dynamic_arr のサイズ (sizeof): %zu\n", sizeof(dynamic_arr)); // ポインタのサイズが返される!
// free(dynamic_arr);
return 0;
}
“`
静的に宣言された配列(コンパイル時にサイズが決まる配列)に対しては、この sizeof
を使った要素数計算は安全かつ正確です。しかし、動的に確保したメモリ領域を指すポインタに対して同じ計算をしようとすると、ポインタのサイズが返されてしまい、意図した結果になりません。動的に確保した領域のサイズや要素数は、確保した時に自分で管理する必要があります。
5.3 データ構造のサイズ計算
連結リストやツリーのようなデータ構造を作成する際、ノード(要素)は通常、構造体として定義されます。これらのノードを動的に作成する場合、やはり malloc
や calloc
を使いますが、その際にノード構造体一つあたりのサイズを sizeof
で取得します。
例 5.2: 連結リストのノードサイズ
“`c
include
include
// 連結リストのノードを表す構造体
struct Node {
int data; // データ部分
struct Node *next; // 次のノードへのポインタ
};
int main() {
// struct Node 型のサイズを知る
size_t node_size = sizeof(struct Node);
printf("連結リストのノード (struct Node) のサイズ: %zu バイト\n", node_size);
// ノードを動的に作成する例
struct Node *new_node = (struct Node*)malloc(node_size);
if (new_node == NULL) {
perror("ノードのメモリ確保に失敗しました");
return 1;
}
new_node->data = 100;
new_node->next = NULL;
printf("新しいノードをメモリに確保しました。\n");
// 使い終わったら解放
free(new_node);
new_node = NULL;
printf("ノードのメモリを解放しました。\n");
return 0;
}
“`
解説:
- 構造体
struct Node
は、int
型メンバと、同じstruct Node
型を指すポインタメンバを持っています。 sizeof(struct Node)
は、この構造体がメモリ上で占める合計サイズを返します。これには、int
のサイズ、ポインタ (struct Node*
) のサイズ、そしてパディングが含まれる可能性があります。- 動的に新しいノードを作成する際には、この
sizeof(struct Node)
の結果を使ってmalloc
を呼び出し、ノード一つ分の領域を確保します。
このように、sizeof
は複雑なデータ構造を扱う際にも、各要素(ノードなど)が必要とするメモリサイズを知るために不可欠です。
第6章:まとめと次のステップ
この記事では、C言語の sizeof
演算子について、その基本的な使い方から、様々なデータ型への適用、そして注意点や実践的な使い方までを詳しく解説しました。
今回の学びを振り返りましょう:
sizeof
演算子は、データ型や変数、式がメモリ上で占めるバイト数を返します。- 基本的な使い方は
sizeof(型名)
またはsizeof 変数名/式
です。型名の場合は括弧が必須です。 char
型は1バイトですが、他の基本データ型(int
,float
,double
など)のサイズはシステムやコンパイラによって異なります。- 配列に
sizeof
を適用すると、配列全体のサイズが返されます。要素数を計算する際はsizeof(配列名) / sizeof(配列名[0])
というテクニックが使えます(ただし、関数に渡された配列には使えません!)。 - ポインタ変数に
sizeof
を適用すると、ポインタ変数自体のサイズ(通常4バイトまたは8バイトなど、システム依存)が返されます。ポインタが指す先のデータのサイズを知るにはsizeof(*ポインタ変数)
とします。 - 構造体 (
struct
) のサイズは、メンバのサイズの合計に加えて、アライメントのためのパディングが含まれるため、メンバの合計サイズよりも大きくなることがあります。 - 共用体 (
union
) のサイズは、最も大きいメンバのサイズになります(アライメント考慮あり)。 - 列挙型 (
enum
) のサイズは、通常int
型と同じになります。 sizeof
はほとんどの場合、コンパイル時定数として評価されますが、可変長配列 (VLA) に対しては実行時評価になることがあります。- 関数名や不完全型には
sizeof
は適用できません。 - 最も重要な注意点として、関数に配列を渡した場合、それはポインタとして扱われるため、関数内で
sizeof
を使って配列全体のサイズや要素数を正確に知ることはできません。 サイズ情報は別途引数として渡す必要があります。 sizeof
は、動的メモリ確保 (malloc
,calloc
) やデータ構造のサイズ計算など、C言語の多くの場面で不可欠なツールです。
sizeof
演算子を正しく理解し、使いこなすことは、C言語でメモリを意識した効率的かつ安全なプログラミングを行う上で非常に重要です。特に、ポインタ、配列、構造体と sizeof
の関係は、C言語の学習において避けては通れない道です。
もしこの記事を読んで分からない点があれば、実際にコードを書いて実行し、様々なデータ型や状況で sizeof
の結果を確認してみてください。手を動かすことが理解を深める一番の方法です。
sizeof
をマスターした皆さんは、C言語のメモリ管理という旅の次のステップに進む準備ができています。動的メモリ確保の詳細、ポインタ演算のさらに詳しい規則、そして様々なデータ構造の実装など、学ぶべきことはたくさんあります。
この知識が、皆さんのC言語学習の助けとなれば幸いです。頑張ってください!
補足:
この記事では、sizeof
の返す型である size_t
を表示するために printf
の %zu
フォーマット指定子を使用しました。これはC99規格で導入されたものです。古いコンパイラやシステムでは %u
(unsigned int) や %lu
(unsigned long) を使用する必要がある場合もありますが、現代のC言語開発では %zu
が推奨されます。
また、printf
でポインタのアドレスを表示する際には %p
フォーマット指定子を使用し、対応する引数を (void*)
にキャストするのが一般的です。
これらの細かい点も、C言語を正確に扱う上で徐々に慣れていくと良いでしょう。
これで、C言語の sizeof
演算子についての約5000語の詳細な解説記事は完了です。