こんにちは。田原です。
まず、この記事は初心者C++er Advent Calendar 2016の10日目の記事です。
C/C++言語では配列がポインタ扱いされる場面があります。配列のままなら要素数が残っているのですか、ポインタに成ると要素数が抜け落ちてしまいます。その辺りを少し解説してみます。
1.配列の要素数取り出し定番マクロ
C/C++言語で配列の要素数を取り出す比較的知られているマクロを使う方法です。
#include <iostream> #define ARRAY_SIZE(dArray) (sizeof(dArray)/sizeof(dArray[0])) int main() { int aArray[10]={}; std::cout << "ARRAY_SIZE(aArray) = " << ARRAY_SIZE(aArray) << "\n"; return 0; } // 実行結果 // ARRAY_SIZE(aArray) = 10
配列の定義に直接アクセスできる場合は、sizeof(配列型の変数)はその配列のバイト数を返します。そこで、その値を配列の要素1つのバイト数で割ることで配列の要素数を計算しています。
例えば、int array[10];
の時sizeof(array)はarrayのバイト数なのでint型が4バイトの場合、40になります。
次に、皆さんご存知のように配列変数はポインタへ代入できます。例えば、int* p=array;
で代入します。そして、混乱しやすいのですが、sizeof(array)は配列のバイト数を返しますが、sizeof(p)はポインタ型のバイト数を返します。つまり、sizeof(ポインタ変数)はポインタ変数のバイト数を返却しますから、64bitビルドなら8が返却されます。
#include <iostream> #define ARRAY_SIZE(dArray) (sizeof(dArray)/sizeof(dArray[0])) void foo(int* iArray) { // ポインタiArrayのバイト数が表示されます std::cout << "sizeof(iArray) = " << sizeof(iArray) << "\n"; } int main() { int aArray[10]={}; // 配列aArrayのバイト数が表示されます std::cout << "sizeof(aArray) = " << sizeof(aArray) << "\n"; int* p=aArray; // ポインタpのバイト数が表示されます std::cout << "sizeof(p) = " <<sizeof(p) << "\n"; foo(aArray); return 0; } // 実行結果: // sizeof(aArray) = 40 // sizeof(p) = 8 // sizeof(iArray) = 8
なお、int型が4バイト、ポインタ型が8バイトの処理系における結果です。
Visual C++やgccのIntel CPU向けコンパイラで64ビット・ビルドした場合が該当します。
2.関数へ渡す時
というわけで、関数へ配列をポインタ型で渡してしまうと、関数側では配列の要素数を取り出せません。
#include <iostream> #define ARRAY_SIZE(dArray) (sizeof(dArray)/sizeof(dArray[0])) void foo(int* iArray) { std::cout << "ARRAY_SIZE(iArray) = " << ARRAY_SIZE(iArray) << "\n"; } int main() { int aArray[10]={}; std::cout << "ARRAY_SIZE(aArray) = " << ARRAY_SIZE(aArray) << "\n"; foo(aArray); return 0; } // 実行結果: // ARRAY_SIZE(aArray) = 10 // ARRAY_SIZE(iArray) = 2
この例では、ポインタ型が8バイト、int型が4バイトの環境で走らせています。
8/4=2なのでARRAY_SIZE(iArray)は2を返却します。
3.要素を指定する方法
受け取る配列の要素数を指定することができます。書き方は直感的ではないので丸暗記が良いと思います。(私はもう歳なので暗記が辛くて必要な時に検索してます。)
記述 | 意味 |
---|---|
void foo( int (*iArray)[10] ) { } | int[10]型へのポインタ |
void foo( int (&iArray)[10] ) { } | int[10]型の参照 |
(C++言語ではint[10]と表現ができることろは限られていますが、便宜的に10個の要素を持つint型配列の意味で使っています。)
しかし、ポインタ型は混乱しますのであまり使わない方が良いです。
下記を見て下さい。iArray[0]はaArrayのアドレスと同じです。そしてiArray[1]のアドレスは、それに0x28=40バイト加えた値になってます。40バイトはaArrayサイズと同じです。iArrayはint[10]型へのポインタですので、その次の要素のアドレスはsizeof(int[10])を加えたアドレスになるわけです。
#include <iostream> #define ARRAY_SIZE(dArray) (sizeof(dArray)/sizeof(dArray[0])) void foo(int (*iArray)[10]) { std::cout << "iArray[0] = " << iArray[0] << "\n"; std::cout << "iArray[1] = " << iArray[1] << "\n"; } int main() { int aArray[10]={}; std::cout << "sizeof(aArray) = " << sizeof(aArray) << "\n"; std::cout << "aArray = " << aArray << "\n"; foo(&aArray); return 0; } // 実行結果: // sizeof(aArray) = 40 // aArray = 0x60fe20 // iArray[0] = 0x60fe20 // iArray[1] = 0x60fe48
ですので、次のように参照を使った方がミスをし難いと思います。
#include <iostream> #define ARRAY_SIZE(dArray) (sizeof(dArray)/sizeof(dArray[0])) void foo(int (&iArray)[10]) { std::cout << "ARRAY_SIZE(iArray) = " << ARRAY_SIZE(iArray) << "\n"; } int main() { int aArray[10]={}; std::cout << "ARRAY_SIZE(aArray) = " << ARRAY_SIZE(aArray) << "\n"; foo(aArray); return 0; } // 実行結果: // ARRAY_SIZE(aArray) = 10 // ARRAY_SIZE(iArray) = 10
4.異なるサイズの配列を受け取る
上述の最後の例のfooが受け取るのはint[10]型の参照ですので、要素数10個以外の配列は渡せません。任意の要素数の配列を受け取りたいこともあります。そのような時は、以下の用に関数テンプレートにすることで受け取れるようになります。
#include <iostream> #define ARRAY_SIZE(dArray) (sizeof(dArray)/sizeof(dArray[0])) template<typename tArray> void foo(tArray& iArray) { std::cout << "ARRAY_SIZE(iArray) = " << ARRAY_SIZE(iArray) << "\n"; } int main() { int aArray[10]={}; std::cout << "ARRAY_SIZE(aArray) = " << ARRAY_SIZE(aArray) << "\n"; foo(aArray); int aArray2[20]={}; std::cout << "ARRAY_SIZE(aArray2)= " << ARRAY_SIZE(aArray2) << "\n"; foo(aArray2); return 0; } // 実行結果: // ARRAY_SIZE(aArray) = 10 // ARRAY_SIZE(iArray) = 10 // ARRAY_SIZE(aArray2)= 20 // ARRAY_SIZE(iArray) = 20
5.配列の要素数を取り出す関数テンプレート
特にメリットがあるわけではないですが、マクロを使わないで配列の要素数を取り出す方法もあります。C++11規格で導入されたtype_traitsを使えば下記のように書けます。(type_traitsを使わなくても書けますが、必要性はほぼないと思いますので割愛します。)std::extentは受け取った型が配列なら、その要素数を返却します。
#include <iostream> #include <type_traits> template<typename tArray> std::size_t getArraySize(tArray& iArray) { return std::extent<tArray>::value; } template<typename tArray> void foo(tArray& iArray) { std::cout << "getArraySize(iArray) = " << getArraySize(iArray) << "\n"; } int main() { int aArray[10]={}; std::cout << "getArraySize(aArray) = " << getArraySize(aArray) << "\n"; foo(aArray); int aArray2[20]={}; std::cout << "getArraySize(aArray2)= " << getArraySize(aArray2) << "\n"; foo(aArray2); return 0; } // 実行結果: // getArraySize(aArray) = 10 // getArraySize(iArray) = 10 // getArraySize(aArray2)= 20 // getArraySize(iArray) = 20
普通は
template
void foo(const T (&iArray)[N])
{
std::cout << "ArraySize = " << N <C/C++言語ではint[10]と言う表現はできませんが
そうでもない
http://melpon.org/wandbox/permlink/DVs1mVxyB7m18aTD
指摘、ありがとうございます。
そう言えばそうでした。完全に抜け落ちてました。
記事を修正します。