Sizeof

主にCC++において、sizeofは、データ型の大きさを求める単項演算子である。sizeofは原則としてコンパイル時計算される演算子で、もしくは括弧でくくった型指定子を与えるとその大きさをバイト単位で返す。これは組み込みの数値型(整数型浮動小数点数型)、列挙型ポインタ型、利用者定義の複合データ型(構造体共用体、C++のクラス)まで、ほぼ全てのデータ型に対して使用できる。

必要性

[編集]

多くのプログラムで、データ型の大きさがわかると便利な状況がある。最もよくある例としては標準Cライブラリmallocなどによる動的メモリ確保が挙げられる。組込型の大きさは処理系定義となっており、厳密な大きさはsizeof (char)1であることを除いて標準に定められていない。次の例では10個の要素を持つint型の配列を格納するのに十分なメモリを確保しようとしている。(処理系に依存したコードを書くつもりでなければ)int型の正確な大きさはわからないのでsizeofが必要となる。

int *pointer; /* intへのポインタ型、メモリ確保したデータを参照する */
pointer = malloc(sizeof (int) * 10);

このコードで、mallocは確保したメモリ領域へのポインタを返すが、その大きさはちょうどint型10個分になる。

一般的に、CとC++で型の大きさを仮定するのは安全でない。標準規格でもchar以外の型がメモリ上で何バイト占めるかを規定していない。たとえば、16ビットシステムでのint型の大きさは通例2バイトであるが、大半の32ビットシステムではint型の大きさは4バイトである。また、構造体や共用体ではアライメントもあり、ますます正確な大きさを求めるのは困難となる。そのようなこともあり、移植性の高いプログラムを書くには型の大きさを求めるためにsizeofを使用することが推奨されている。

CとC++

[編集]

使用方法

[編集]

sizeof演算子は、メモリ上に領域を占めるものであれば、ほとんどどんなものに対しても使用できる。sizeofを使うには、キーワードsizeofの後に変数やあるいは括弧でくくった上で型名を書く。変数や式の場合は、括弧でくくるかどうかは自由である。次の例ではint型がchar型の4倍のサイズを持つ実装の場合、1, 4と出力される(なお、char型にsizeof演算子を適用した結果は全ての実装において1でなければならない)。

// C99
char c;
printf("%zu, %zu", sizeof c, sizeof (int));
// Microsoft Visual C++
char c;
printf("%Iu, %Iu", sizeof c, sizeof (int));
// キャスト
char c;
printf("%lu, %lu", (unsigned long) sizeof c, (unsigned long) sizeof (int));

sizeofの結果は実装定義の符号無し整数型であるsize_t型となり、負数になることはない。

sizeofを関数型、ビットフィールド、不完全型(後述)に対して使用することはできない。

なお、printfなどでsize_t型を書式出力する場合、C99で追加された長さ修飾子zを用いる。 C99に対応していない処理系では、引数をキャストするか、処理系依存の修飾子(たとえばMicrosoft Visual C++ならば修飾子I)を用いる。

もし%uを使用すると、size_t型とunsigned int型のサイズが異なる環境において未定義動作となる。

配列に対するsizeof

[編集]

sizeof配列型に使用されると、その配列がメモリ上に占める大きさが演算結果となる。配列の大きさは、要素の型の大きさに要素数をかけた値と規定されており、例えば要素型Tについてsizeof (T[8])sizeof (T) * 8と同じ値になる。逆に、配列xに対して、sizeof x / sizeof x[0]の演算により、配列の要素数を求めることが可能である。

次の例では、文字の複写時にバッファオーバーランを起こさないよう、sizeofを配列の大きさを求めるために用いている。

/* sizeofを配列に使用する例 */
#include <stdio.h>
#include <string.h>

int main(void)
{
  char buffer[10]; /* 要素数10のchar配列 */

  /* 標準入力から読み取った結果をbufferへ9文字までのみ複写する。
   *(sizeof (char)は常に1なので、sizeof buffer[0]で割る必要はない)
   */
  fgets(buffer, sizeof buffer, stdin);

  puts(buffer);
  return 0;
}

C99可変長配列に対して sizeofを適用する場合、配列の大きさは実行時に動的に計算され、コンパイル時定数にはならない。

sizeofと不完全型

[編集]

sizeofは完全に定義されたデータ型(メモリレイアウトが確定した型)のみに適用できる。配列なら、要素数が変数宣言に含まれていなければならず、構造体や共用体ならメンバが完全に定義されていなければならない。例えば次の二つのソースファイルがあったとする。

/* file1.c */
int arr[10];
struct x { int one; int two; };
/* file2.c */
extern int arr[];
struct x;

どちらのファイルも正しいCのソースであるものの、"file1.c"ではsizeofarrstruct xに使用できるが、"file2.c"では完全型でないため使用できない。"file2.c"ではarrの要素数がわからず、struct xのメンバも分からないためである。"file2.c"でarrstruct xsizeofで使用できるようにするには、"file2.c"のarrの宣言に要素数を指定したり、struct xの完全な宣言を書いたりする必要がある。

構造体メンバのsizeof

[編集]

構造体やクラスの非静的メンバに対してsizeofを適用する場合、CおよびC++03規格までのC++では、構造体やクラスのオブジェクト(インスタンス)からメンバにアクセスする式に対して適用しなければならない。

#include <stdio.h>

struct my_type {
    int member1;
    short member2;
};

int main(void) {
    struct my_type obj;
    printf("%u\n", (unsigned)sizeof(obj.member1));
    printf("%u\n", (unsigned)sizeof(obj.member2));
    /* sizeofはコンパイル時に評価されるので、以下のように書いても実行時のNULLデリファレンスは発生しない */
    printf("%u\n", (unsigned)sizeof(((struct my_type*)NULL)->member1));
    printf("%u\n", (unsigned)sizeof(((struct my_type*)NULL)->member2));
    return 0;
}

C++11規格以降では、スコープ解決演算子::を利用して以下のように書けるようになった[1]

#include <cstdio>

struct my_type {
    int member1;
    short member2;
};

int main() {
    printf("%zu\n", sizeof(my_type::member1));
    printf("%zu\n", sizeof(my_type::member2));
}

GCCなど、一部のコンパイラでは、C++03以前でもこの記法を拡張としてサポートしていた。

実装

[編集]

コンパイラは言語の実装に適合するように、sizeof演算子をデータ型のメモリ上に占める大きさを結果とするように実装しなければならない。また(既述の例外を除いて)これはコンパイル時計算される演算子であり、アセンブリ言語上では単なる即値になる。

構造体のパディング

[編集]

利用者定義型の大きさはアライメントのためにメンバの大きさの合計よりも大きくなることが規格では許されている。次のコードは多くの環境において8と出力される。

struct student {
    char grade; /* charは1バイト */
    int age /* intは4バイト */
};

printf("%zu", sizeof (struct student));

この理由は、多くのコンパイラでは通常ワード単位にデータを揃えるためであり、個々のメンバも境界を揃えられる。上の場合は、境界調整によってメンバ変数のageが次のワード単位に置かれる。このような構造体には境界調整のためにメンバ間やメンバの後ろに「パディング」と呼ばれる余分な隙間が置かれる。多くのCPUではデータがワード単位のメモリアドレスに置かれていた方が高速に読み書きでき、また中にはワード単位に揃えられていないと読み書きできないCPUもある[2]

D言語では全ての型が持っているプロパティとしてsizeofが用意されている。

void main()
{
    writefln(int.sizeof);
}

.NET

[編集]

.NET Framework/.NET Coreでは、Marshal.SizeOfメソッド[3]によってオブジェクトのアンマネージサイズやアンマネージ型のサイズをバイト単位で取得可能である。.NET 4.5.1でジェネリックバージョンのオーバーロードが追加された。そのほか、各.NET言語に類似の組み込み言語機能が用意されていることがあるが、System.Boolean型に対する演算結果など、必ずしもMarshal.SizeOfと同じ結果になるとは限らない。

C++/CLI

[編集]

C++/CLIsizeof演算子はネイティブ型(基本型およびポインタ型)に用いる限りコンパイル時定数となり、基本的にC++と同じである。しかし値クラス (value class) 型、(マネージ)ハンドル型やジェネリック型引数に対して用いられたときにはコンパイル時定数でなくなる。また参照クラス (ref class) 型やインターフェイス型に対して用いることはできない(不正となる)[4]

C#

[編集]

C#sizeof演算子は、アンマネージ型(組み込み型、列挙型、ポインタ型、参照型のフィールドやプロパティを含まないユーザー定義の構造体)のサイズをバイト単位で取得する。結果はint型となる。特定の組み込み型に対してsizeof演算子を用いた場合の結果はコンパイル時定数となるが、それ以外の型に使用した場合はコンパイル時定数とならない。また、sizeof演算子を使用するにはunsafeモードが必要だが、C# 2.0以降は組み込み型に対するsizeofに関してのみunsafeが不要となった[5]

Visual Basic

[編集]

Visual Basicでは、Len関数などが存在する。Lenは文字列を引数に与えると文字列の長さを返すが、その他の型の変数を与えると変数の大きさを返す。

ActiveBasic

[編集]

ActiveBasicでは、Visual Basicと同様のLen組込関数を持っているほか、SizeOf組込関数を持っている。Lenは型名を指定することができないが、SizeOfは型名を指定できる。逆にSizeOfに変数や式を指定することはできない。

脚注

[編集]

関連項目

[編集]