よく教えられている標準の配列サイズのマクロは
#define ARRAYSIZE(arr) (sizeof(arr) / sizeof(arr[0]))
または同等のフォーメーション。ただし、この種のことは、ポインターが渡されたときに暗黙のうちに成功し、不思議なことにバラバラになるまで、実行時にもっともらしい結果が得られます。
この間違いを犯すのはとても簡単です。ローカル配列変数を持つ関数はリファクタリングされ、配列操作をビットとしてパラメータとして呼び出される新しい関数に移動します。
したがって、問題は、CのARRAYSIZE
マクロの誤用を、できればコンパイル時に検出するための「衛生的な」マクロがあるかどうかです。 C++では、配列引数のみに特化したテンプレートを使用します。 Cでは、配列とポインタを区別する方法が必要になるようです。 (たとえば、配列を拒否したい場合は、たとえば(arr=arr, ...)
配列の割り当てが無効であるため)。
Linuxカーネルは、この問題に対処するためにARRAY_SIZE
のNice実装を使用しています。
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]) + __must_be_array(arr))
と
#define __must_be_array(a) BUILD_BUG_ON_ZERO(__same_type((a), &(a)[0]))
そして
#define __same_type(a, b) __builtin_types_compatible_p(typeof(a), typeof(b))
もちろん、これはGNU Cでのみ移植可能です。これは、2つの組み込み関数:typeof
演算子と__builtin_types_compatible_p
関数を使用するためです。また、 "有名な" BUILD_BUG_ON_ZERO
マクロはGNU Cでのみ有効です。
コンパイル時の評価要件(これは私たちが望むことです)を想定すると、このマクロの移植可能な実装はわかりません。
「セミポータブル」な実装(すべてのケースをカバーするわけではありません)は次のとおりです。
#define ARRAY_SIZE(arr) \
(sizeof(arr) / sizeof((arr)[0]) + STATIC_EXP(IS_ARRAY(arr)))
と
#define IS_ARRAY(arr) ((void*)&(arr) == &(arr)[0])
#define STATIC_EXP(e) \
(0 * sizeof (struct { int ARRAY_SIZE_FAILED:(2 * (e) - 1);}))
gcc
を使用すると、引数が-std=c99 -Wall
の配列の場合は警告が表示されませんが、-pedantic
は警告を表示します。理由は、IS_ARRAY
式が整数定数式ではなく(ポインター型へのキャストと添字演算子は整数定数式では許可されない)、STATIC_EXP
のビットフィールド幅には整数定数式が必要です。
このバージョンのARRAYSIZE()
は、arr
がポインターの場合は0
を返し、純粋な配列の場合はサイズを返します
#include <stdio.h>
#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (IS_ARRAY(arr) ? (sizeof(arr) / sizeof(arr[0])) : 0)
int main(void)
{
int a[5];
int *b = a;
int n = 10;
int c[n]; /* a VLA */
printf("%zu\n", ARRAYSIZE(a));
printf("%zu\n", ARRAYSIZE(b));
printf("%zu\n", ARRAYSIZE(c));
return 0;
}
出力:
5
0
10
ベン・ジャクソンが指摘したように、ランタイム例外を強制できます(0で除算)
#define IS_INDEXABLE(arg) (sizeof(arg[0]))
#define IS_ARRAY(arg) (IS_INDEXABLE(arg) && (((void *) &arg) == ((void *) arg)))
#define ARRAYSIZE(arr) (sizeof(arr) / (IS_ARRAY(arr) ? sizeof(arr[0]) : 0))
悲しいことに、コンパイル時のエラーを強制することはできません(arg
のアドレスは実行時に比較する必要があります)
型パラメーターの代わりにtypeofを使用したブラスの回答の変更:
#define ARRAY_SIZE(A) \
_Generic(&(A), \
typeof((A)[0]) **: (void)0, \
default: sizeof(A) / sizeof((A)[0]))
C11では、_Generic
を使用して配列とポインターを区別できますが、エレメントタイプを指定した場合にのみ、その方法を見つけました。
#define ARRAY_SIZE(A, T) \
_Generic(&(A), \
T **: (void)0, \
default: _Generic(&(A)[0], T *: sizeof(A) / sizeof((A)[0])))
int a[2];
printf("%zu\n", ARRAY_SIZE(a, int));
マクロは以下をチェックします。1)Aへのポインターはポインターへのポインターではありません。 2)pointer-to-elemはpointer-to-Tです。 (void)0
と評価され、ポインターで静的に失敗します。
それは不完全な答えですが、おそらく読者はそれを改善してその型パラメーターを取り除くことができます!
gcctypeof extension に依存する別の例を次に示します。
#define ARRAYSIZE(arr) ({typeof (arr) arr ## _is_a_pointer __attribute__((unused)) = {}; \
sizeof(arr) / sizeof(arr[0]);})
これは、同一のオブジェクトを設定し、配列指定のイニシャライザで初期化することで機能します。配列が渡された場合、コンパイラーは満足しています。ポインターが渡された場合、コンパイラーは次のメッセージを表示します。
arraysize.c: In function 'main':
arraysize.c:11: error: array index in non-array initializer
arraysize.c:11: error: (near initialization for 'p_is_a_pointer')
GNUと呼ばれる拡張機能 ステートメント式 を使用した解決策の1つを次に示します:
_#define ARRAYSIZE(arr) \
({typedef char ARRAYSIZE_CANT_BE_USED_ON_POINTERS[sizeof(arr) == sizeof(void*) ? -1 : 1]; \
sizeof(arr) / sizeof((arr)[0]);})
_
これは 静的アサーション を使用してsizeof(arr) != sizeof(void*)
をアサートします。これには明らかな制限があります-たまたまちょうど1つのポインタ(たとえば、ポインタ/整数の1長の配列、または32ビットの4長のバイト配列)のサイズの配列では、このマクロを使用できませんプラットホーム)。しかし、それらの特定のインスタンスは十分に簡単に回避できます。
このソリューションは、このGNU拡張機能をサポートしていないプラットフォームには移植できません。そのような場合は、標準のマクロを使用することをお勧めします。誤ってマクロへのポインターを渡す心配はありません。
ひどい、はい、しかしそれは機能し、それは移植可能です。
#define ARRAYSIZE(arr) ((sizeof(arr) != sizeof(&arr[0])) ? \
(sizeof(arr)/sizeof(*arr)) : \
-1+0*fprintf(stderr, "\n\n** pointer in ARRAYSIZE at line %d !! **\n\n", __LINE__))
これはコンパイル時に何も検出しませんが、stderr
にエラーメッセージを出力し、それがポインターまたはの場合は-1
を返します長さは1です。
==> [〜#〜] demo [〜#〜] <==
私の個人的なお気に入り、gcc 4.6.3と4.9.2を試してみました。
#define STR_(tokens) # tokens
#define ARRAY_SIZE(array) \
({ \
_Static_assert \
( \
! __builtin_types_compatible_p(typeof(array), typeof(& array[0])), \
"ARRAY_SIZE: " STR_(array) " [expanded from: " # array "] is not an array" \
); \
sizeof(array) / sizeof((array)[0]); \
})
/*
* example
*/
#define not_an_array ((char const *) "not an array")
int main () {
return ARRAY_SIZE(not_an_array);
}
コンパイラのプリント
x.c:16:12: error: static assertion failed: "ARRAY_SIZE: ((char const *) \"not an array\") [expanded from: not_an_array] is not an array"
コレクションのもう1つの例。
#define LENGTHOF(X) ({ \
const size_t length = (sizeof X / (sizeof X[0] ?: 1)); \
typeof(X[0]) (*should_be_an_array)[length] = &X; \
length; })
長所:
短所: