【C言語】sizeof演算子とは?sizeof演算子を利用するメリットは?

C言語のsizeof演算子の解説ページアイキャッチ

このページにはプロモーションが含まれています

このページでは、C言語における「sizeof 演算子」および「sizeof 演算子を使用するメリット」について解説していきます!

sizeof 演算子とは

sizeof 演算子とは、変数や型等のデータサイズを「バイト数」で取得する演算子となります。

C言語では型ごとにデータサイズが異なります。また、変数も宣言時に指定した型に応じてデータサイズ決まり、宣言によってそのデータサイズ分のメモリが使用できるようになります。

sizeof 演算子は、こういった型のデータサイズ・変数のデータサイズが実際に何バイトであるかを調べる(取得する)ための演算子となります。

具体的には、sizeof の後ろ側に 型名変数名 を指定することで、それらのデータサイズを取得することが出来ます。取得されたサイズの単位は「バイト」となります。

sizeof演算子
sizeof(変数名);
sizeof(型名);

例えば下記のように sizeof 演算子を実行すれば、1つ目の printf では int 型のデータサイズが、2つ目の printf では変数 x のデータサイズがそれぞれ表示されます。

sizeof演算子の利用例
#include <stdio.h>

int main(void) {

    double x;

    printf("%zu\n", sizeof(int));
    printf("%zu\n", sizeof(x));
    
    return 0;
}

ちなみに、sizeof 演算子の後に括弧をつけなくても良い場合があります(型の場合は括弧が必須)。また、このページでは型と変数のみに対して sizeof を利用する例を紹介していきますが、例えば数値そのものに対して sizeof 演算子を使用することも可能です。

リテラルへのsizeof演算子の利用
printf("%zu\n", sizeof 1234567890); /* これはOK */
printf("%zu\n", sizeof long); /* これは NG */

sizeof 演算子の利用例

次は sizeof 演算子の利用例を確認していきましょう!

スポンサーリンク

sizeof 演算子で型のサイズを取得する

前述の通り、sizeof 演算子では型のサイズを調べることが出来ます。

int 型や double 型といった組込み型だけでなく、あなたが作成した構造体型のサイズ、さらにはポインタ型のサイズ等も調べることも可能です。

下記は、さまざまな型のサイズを取得して表示する例となります。

型のサイズを取得する
#include <stdio.h>

typedef struct _my_data {
    int a;
    char b;
    int c;
    double d;
    int *e;
    char f;
} MY_DATA;

int main(void) {

    printf("sizeof int : %zu\n", sizeof(int));
    printf("sizeof double : %zu\n", sizeof(double));
    printf("sizeof char : %zu\n", sizeof(char));
    printf("sizeof MY_DATA : %zu\n", sizeof(MY_DATA));
    printf("sizeof FILE : %zu\n", sizeof(FILE));
    printf("sizeof int* : %zu\n", sizeof(int*));
    printf("sizeof char* : %zu\n", sizeof(char*));
    printf("sizeof void* : %zu\n", sizeof(void*));
    printf("sizeof MY_DATA* : %zu\n", sizeof(MY_DATA*));

    return 0;
}

私の環境で実行すると下記のように表示されました。

sizeof int : 4
sizeof double : 8
sizeof char : 1
sizeof MY_DATA : 40
sizeof FILE : 152
sizeof int* : 8
sizeof char* : 8
sizeof void* : 8
sizeof MY_DATA* : 8

この実行結果に対するポイントは下記の3つだと思います。

実行結果は環境によって異なる可能性あり

int 型やポインタ型のサイズは環境(使用するコンパイラ等)によって異なることがあります。

そのため、上記を実行しても私の実行結果と異なる可能性があります。

ポインタの型のサイズは全て同じ

環境によってサイズは異なる可能性はあるものの、同一の環境であればポインタ型のサイズは全て同じになるはずです。これは、ポインタ型はデータそのものではなく、アドレスを格納する型であるからです。

sizeof 演算子により正しくサイズが取得可能

さて、上記のソースコードでは構造体 MY_DATA を定義していますが、その MY_DATA のサイズは実行結果では 40 と表示されています。

その一方で、MY_DATA には下記の型のメンバが存在しますので、単純に各型のサイズを足し合わせれば 26 となるはずです。

  • int 型(4バイト):2つ
  • char 型(1バイト):2つ
  • double 型(8バイト):1つ
  • int * 型(8バイト):1つ

詳しい説明は省略しますが、構造体のメンバは単にメモリ上に敷き詰めるように配置されるのではなく、処理しやすいように隙間を空けて配置されることがあります。その隙間(パディング)の分、構造体のサイズはメンバの型のサイズの合計よりも大きくなることがあります。

そのため、構造体のサイズをメンバの型の合計で求めると、実際の構造体のサイズと異なる可能性があります。ですが、sizeof 演算子を利用すれば、そういった隙間も考慮して構造体のサイズを求めてくれ、正しい型のサイズを取得することが可能です。

sizeof 演算子で変数のサイズを取得する

また、前述の通り、sizeof 演算子では変数のサイズを調べることも出来ます。

下記は、いろんな変数のサイズを取得して表示する例となります。

変数のサイズを取得する
#include <stdio.h>

typedef struct _my_data {
    int a;
    char b;
    int c;
    double d;
    int *e;
    char f;
} MY_DATA;

int main(void) {

    int a;
    int b;
    int *c;
    MY_DATA d;
    int e[1000];
    double f[1000];
    int *g = e;

    printf("sizeof a : %zu\n", sizeof(a));
    printf("sizeof b : %zu\n", sizeof(b));
    printf("sizeof c : %zu\n", sizeof(c));
    printf("sizeof d : %zu\n", sizeof(d));
    printf("sizeof e : %zu\n", sizeof(e));
    printf("sizeof f : %zu\n", sizeof(f));
    printf("sizeof e[0] : %zu\n", sizeof(e[0]));
    printf("sizeof e[999] : %zu\n", sizeof(e[999]));
    printf("sizeof g : %zu\n", sizeof(g));

    return 0;
}

私の環境で実行すると下記のように表示されました。

sizeof a : 4
sizeof b : 4
sizeof c : 8
sizeof d : 40
sizeof e : 4000
sizeof f : 8000
sizeof e[0] : 4
sizeof e[999] : 4
sizeof g : 8

この実行結果に対するポイントは下記の4つだと思います。

単なる変数のサイズは型のサイズと一致

まず、sizeof 演算子に「単なる変数」や「ポインタ変数」の変数名を指定した場合、データサイズは型のものと一致します。

配列名指定時に取得されるのは配列全体のデータサイズ

その一方で、sizeof 演算子に「配列名」を指定した場合は配列全体のサイズ、すなわち 要素1つのサイズ * 要素数 が変数のデータサイズとして取得されることになります。

取得される値は 要素数 ではないので注意してください。要素数 を取得する方法については、次の sizeof 演算子を利用して配列の要素数を取得する で解説します。

配列を指すポインタ変数を指定しても取得できるのはポインタ変数のサイズ

ただし、sizeof 演算子に「配列のアドレスを格納しているポインタ変数」を指定したとしても、そのポインタ変数自体のデータサイズ、すなわちポインタ型のデータサイズが取得されることになります。

配列全体のデータサイズである 要素1つのサイズ * 要素数 を取得したい場合は、sizeof 演算子に「配列名」を指定する必要がありますので注意してください。

配列の要素指定時に取得されるのはその要素のサイズ

また、sizeof 演算子に「配列の要素」を直接指定した場合は、その要素単体のデータサイズが取得されることになります。特に配列が1次元配列の場合は、この取得されるサイズは配列の型のデータサイズに一致します。

sizeof 演算子を利用して配列の要素数を取得する

sizeof 演算子は「配列の要素数」を取得する目的で利用することも多いです。

前述の通り、sizeof 演算子に「配列名」を指定した場合は 要素1つのサイズ * 要素数 を配列全体のデータサイズとして取得することが出来ます。

これを利用して、sizeof 演算子を利用して配列の 要素数 を取得することが可能です。

sizeof 演算子に「配列名」を指定した場合は 要素1つのサイズ * 要素数 のデータサイズが取得することができるため、要はこの取得したサイズを 要素1つのサイズ で割ってやることで 要素数 を取得することが可能です。

さらに、要素1つのサイズ は、sizeof 演算子に 配列の要素 を指定することで取得することが可能です。

下記は、sizeof 演算子を利用して配列 a の要素数を取得し、その要素数分のループを行なって配列 a の要素全てを表示する例となります。

要素数分のループ
#include <stdio.h>

int main(void) {

    int a[] = {
        1, 2, 3, 4, 5,
        6, 7, 8, 9, 0
    };
    size_t i;
    size_t num_elem;

    /* 要素数を計算 */
    num_elem = sizeof(a)/sizeof(a[0]);

    for (i = 0; i < num_elem; i++) {
        printf("a[%zu] : %d\n", i, a[i]);
    }

    return 0;
}

コメントにも記載していますが、下記で sizeof 演算子を利用して要素数を取得しています。

要素数の取得
/* 要素数を計算 */
num_elem = sizeof(a)/sizeof(a[0]);

右辺の分子が配列 a要素1つのサイズ * 要素数 となり、分母が配列 a要素1つのサイズ となりますので、結果的に配列 a要素数 を取得することが出来ます(上記の例の場合は num_elem10 となります)。

また、num_elem の型は size_t であり、このように、sizeof 演算子で得られるデータサイズを変数に格納する場合、その変数の型には size_t を使用するのが一番無難です。

注意点1:関数の引数からは配列のデータサイズは取得できない

前述のようにして sizeof 演算子を利用して配列全体のデータサイズを取得することはできるのですが、注意点が2つあるので解説していきます。

1つ目は、関数に引数として配列を渡す場合の注意点で、関数の引数として配列を渡した場合、sizeof 演算子では「配列」のデータサイズは取得できないので注意してください。

例えば、下記のようなソースコードの動作について考えてみましょう。

引数のデータサイズ
#include <stdio.h>

void func(int a[]) {

    printf("[func]sizeof a : %zu\n", sizeof(a));
}

int main(void) {

    int a[] = {
        1, 2, 3, 4, 5,
        6, 7, 8, 9, 0
    };

    printf("[main]sizeof a : %zu\n", sizeof(a));
    
    func(a);

    return 0;
}

main 関数と func 関数両方で sizeof 演算子の結果を表示していますが、私の環境では次のように表示されました。

[main]sizeof a : 40
[func]sizeof a : 8

この結果からも分かるように、関数の引数で配列を受け取ったとしても、sizeof 演算子ではその配列のデータサイズを取得することが出来ません。配列のデータサイズではなくポインタ変数のデータサイズが取得されることになります。

関数の引数で配列を受け取るつもりで引数に int a[] を指定したとしても、結局は int *a、つまり配列のアドレスを格納したポインタ引数として扱われることになります。 

そのため、sizeof 演算子で変数のサイズを取得する で紹介したソースコードの変数 g と同様に扱われ、sizeof 演算子ではポインタ変数のデータサイズが取得されることになります。

したがって、この場合は sizeof 演算子を利用して配列の要素数を取得することもできません。

例えば下記のように sizeof(a) / sizeof(a[0]) を実行しても配列の要素数は取得できず、意図しない回数でのループが実行されることになるので注意してください。

誤った要素数の取得
void func(int a[]) {

    size_t i;
    size_t num_elem;

    num_elem = sizeof(a) / sizeof(a[0]);

    for (i = 0; i < num_elem; i++) {
        printf("[%zu] : %d\n", i, a[i]);
    }
}

関数内で配列の要素数が必要なのであれば、下記のように関数の引数で要素数を渡せるようにしたり、#define 等で定義した定数を参照して要素数が取得したりするなどの対応をする必要があります。

正しい要素数の取得
void func(int a[], size_t num_elem) {

    size_t i;

    for (i = 0; i < num_elem; i++) {
        printf("[%zu] : %d\n", i, a[i]);
    }
}

注意点2:文字数を取得できるわけではない

また、前述の 要素1つのサイズ * 要素数 を 要素1つのサイズ を割ることで求められるのはあくまでも「要素数」であり、文字数が取得できるわけではないので注意してください。

例えば、下記の場合 word_count は配列 a の要素数 100 となり、num elem"Hello World!" の文字数となるわけではないので注意してください。

誤った文字数の取得
#include <stdio.h>

int main(void) {

    char a[100] = "Hello World!";
    size_t word_count;

    word_count = sizeof(a) / sizeof(a[0]);

    printf("word_count : %zu\n", word_count);

    return 0;
}

文字数を取得したい場合は sizeof を利用するのではなく、string.h をインクルードして strlen 等の「文字数を取得する関数」を利用する必要があります。

正しい文字数の取得
#include <stdio.h>
#include <string.h>

int main(void) {

    char a[100] = "Hello World!";
    size_t word_count;

    word_count = strlen(a);

    printf("word_count : %zu\n", word_count);

    return 0;
}

スポンサーリンク

sizeof 演算子を利用して必要なバイト数を計算する

また、C言語の標準ライブラリ関数には引数として「バイト数」を指定する必要があるものが多く存在し、そのバイト数を計算するために sizeof 演算子を利用することも多いです。

例えば malloc 関数などは引数として動的に確保したメモリの「バイト数」を指定する必要があり、この引数に指定するバイト数の計算時に sizeof 演算子を利用することが多いです。

必要なバイト数の計算
#include <stdio.h>
#include <stdlib.h>

int main(void) {

    int *a;
    int i;
    
    /* 確保したいサイズをsizeof演算子を利用して指定 */
    a = malloc(sizeof(int) * 100);
    if (a == NULL) {
        printf("malloc error\n");
        return 0;
    }

    for (i = 0; i < 100; i++) {
        a[i] = i;
    }

    /* aの指すメモリを利用した処理(省略) */

    free(a);

    return 0;
}

また、使用例は省略しますが、memcpy 関数などもコピーしたいサイズをバイト数で指定する必要があるため、この際にも sizeof 演算子を利用することが多いです。

関数の引数等でサイズをバイト数で指定する必要がある際は、sizeof 演算子を利用する方が良い場合が多いと思います。

sizeof 演算子を使うメリット

ここまでの解説の中でも触れてきましたが、最後に sizeof 演算子を使うメリットについて記載しておきます。

正確なサイズが取得できる

sizeof 演算子で型のサイズを取得する で解説したように、型のサイズは環境によって異なりますし、構造体のサイズは単にメンバの型を合計するだけでは正しく求められません。

sizeof 演算子では、自身の環境に合わせた型や変数のサイズを取得することが出来ますし、構造体のサイズも正確に取得することが可能です。

スポンサーリンク

変更容易なソースコードが実現できる

また、sizeof 演算子を上手く利用することで、後からのソースコードの変更が楽に実現できるようにすることも可能です。

例えば下記のように配列 a の全ての要素の値を表示することを考えてみましょう!配列の要素数は 7 ですので、i < 7 が成立している間は処理を継続するように for ループを組んでいます。

sizeofを利用しない場合
int main(void) {

    int a[] = {
        1, 2, 3, 4, 5, 6, 7
    };
    size_t i;

    for (i = 0; i < 7; i++) {
        printf("[%zu] : %d\n", i, a[i]);
    }

    return 0;
}

もちろんこれでもプログラムとして正しく動作させることができるのですが、後からソースコードを変更する時にちょっと不便です。

例えば、配列 a を下記のように変更すると(7 を削除)、配列の要素数が変化し、ループ処理の中で配列の外側にアクセスすることになってしまいます(a[6] にアクセスしてしまう)。

sizeofを利用しない場合の変更
int main(void) {

    int a[] = {
        1, 2, 3, 4, 5, 6
    };
    size_t i;

    for (i = 0; i < 7; i++) {
        printf("[%zu] : %d\n", i, a[i]);
    }

    return 0;
}

そのため、ループの継続条件の部分も変更する必要があります。

その一方で、下記のように sizeof 演算子を利用して配列 a の要素数を求め、その結果を利用してループを組むようにしておけば、

sizeofを利用した場合
int main(void) {

    int a[] = {
        1, 2, 3, 4, 5, 6, 7
    };
    size_t i;
    size_t num_elem;

    num_elem = sizeof(a) / sizeof(a[0]);

    for (i = 0; i < num_elem; i++) {
        printf("[%zu] : %d\n", i, a[i]);
    }

    return 0;
}

後から配列 a を下記のように変更しても(7 を削除)、自動的にループの継続条件も変化しますので、ループの継続条件を手動で変更する必要はありません。

sizeofを利用した場合の変更
int main(void) {

    int a[] = {
        1, 2, 3, 4, 5, 6
    };
    size_t i;
    size_t num_elem;

    num_elem = sizeof(a) / sizeof(a[0]);

    for (i = 0; i < num_elem; i++) {
        printf("[%zu] : %d\n", i, a[i]);
    }

    return 0;
}

このように、sizeof 演算子を利用しておくことで、後からの変更を楽に行うことができるようになります。

また、構造体のサイズなどもソースコードに直接記述しておくと、構造体のメンバの追加や削除を行なった際にサイズに関する処理部分の変更も必要になります。

構造体のサイズを直接指定
/* 40はMY_DATA構造体のサイズ */
MY_DATA *p = malloc(40);

その一方で、構造体のサイズを sizeof 演算子を取得するようにしておけば、構造体のメンバの追加や削除を行なった際にもサイズに関する処理部分の変更は不要になります。

構造体のサイズのsizeofでの指定
MY_DATA *p = malloc(sizeof(MY_DATA));

あまり大したことがないようにも思えるかもしれませんが、重要なのは「今後を見据えてソースコードを簡単に変更できるように工夫する」ことです。

ソースコードは一度書いたらそれで終わりではなく、後からソースコードを変更する機会は多いです。例えばプログラムの仕様が変わった際に変更する必要があるかもしれませんし、そもそもバグがあってそれを修正するためにソースコードを変更する必要があることも多いです。

そういった、後から変更することを見据え、変更しやすいようなソースコードを作成することは重要ですし、特に仕事の場であれば、あなたの仕事の効率やチームの生産性を向上させることにもつながります。

また、これは sizeof 演算子に限った話だけではありません。また、C言語だけでなく全てのプログラミング言語において言えることです。

例えば同じような処理を1つの関数にまとめることも今後のソースコードの変更を見据えた工夫の1つですよね!同じような処理が複数あれば、その処理を変更する際に複数の箇所の変更が必要になりその分手間がかかります。めんどくさいです。

ですが、関数で1つにまとめておけば、変更必要なのはその関数のみとなり、変更の手間が省けます。

もちろんプログラミング始めたての段階であれば、とにかくプログラムが動作するようにプログラミングを頑張るので良いのですが、プログラミングに慣れてきた際には「今後ソースコードを楽に変更するためにはどうすれば良いか?」を考えながらプログラミングに取り組むと、より実践的なプログラミングを体験できると思います。

まとめ

このページでは、C言語における「sizeof 演算子」および「sizeof 演算子を使用するメリット」について解説しました!

sizeof 演算子は変数や型等のデータサイズを「バイト数」で取得する演算子であり、これを利用することで正しく変数や型のデータサイズを取得することが出来ます。データサイズはソースコードに即値で記述するのではなく、sizeof 演算子から取得する形で記述するようにしましょう!

また、配列の要素数を求める際やバイト数を求める際にも sizeof 演算子が活躍します。

さらに、上手く sizeof 演算子を利用することで、後からのソースコードの変更を楽にすることもできます。sizeof 演算子の利用をきっかけに、ソースコードの変更容易性を上げるための工夫にも挑戦していってください!

オススメの参考書(PR)

C言語学習中だけど分からないことが多くて挫折しそう...という方には、下記の「スッキリわかるC言語入門」がオススメです!

まず学習を進める上で、参考書は2冊持っておくことをオススメします。この理由は下記の2つです。

  • 参考書によって、解説の仕方は異なる
  • 読み手によって、理解しやすい解説の仕方は異なる

ある人の説明聞いても理解できなかったけど、他の人からちょっと違った観点での説明を聞いて「あー、そういうことね!」って簡単に理解できた経験をお持ちの方も多いのではないでしょうか?

それと同じで、1冊の参考書を読んで理解できない事も、他の参考書とは異なる内容の解説を読むことで理解できる可能性があります。

なので、参考書は2冊持っておいた方が学習時に挫折しにくいというのが私の考えです。

特に上記の「スッキリわかるC言語入門」は、他の参考書とは違った切り口での解説が豊富で、他の参考書で理解できなかった内容に対して違った観点での解説を読むことができ、オススメです。題名の通り「なぜそうなるのか?」がスッキリ理解できるような解説内容にもなっており、C言語入門書としてもかなり分かりやすい参考書だと思います。

もちろんネット等でも色んな観点からの解説を読むことが出来ますので、分からない点は別の人・別の参考書の解説を読んで解決していきましょう!もちろん私のサイトも参考にしていただけると嬉しいです!

入門用のオススメ参考書は下記ページでも紹介していますので、こちらも是非参考にしていただければと思います。

https://daeudaeu.com/c_reference_book/

同じカテゴリのページ一覧を表示