Cで配列のサイズを決定するにはどうすればよいですか?

2008年09月01日に質問されました。  ·  閲覧回数 2.2M回  ·  ソース

Mark Harrison picture
2008年09月01日

Cで配列のサイズを決定するにはどうすればよいですか?

つまり、配列が保持できる要素の数は?

回答

Mark Harrison picture
2008年09月01日
1337

エグゼクティブサマリー:

int a[17];
size_t n = sizeof(a)/sizeof(a[0]);

完全な答え:

配列のサイズをバイト単位で決定するには、 sizeof演算子を使用できます。

int a[17];
size_t n = sizeof(a);

私のコンピューターでは、intは4バイトの長さなので、nは68です。

配列内の要素の数を決定するために、配列の合計サイズを配列要素のサイズで割ることができます。 次のようなタイプでこれを行うことができます。

int a[17];
size_t n = sizeof(a) / sizeof(int);

正しい答え(68/4 = 17)を取得しますが、 aのタイプを変更した場合、 sizeof(int)も変更し忘れると、厄介なバグが発生します。

したがって、推奨される除数はsizeof(a[0])または同等のsizeof(*a)で、配列の最初の要素のサイズです。

int a[17];
size_t n = sizeof(a) / sizeof(a[0]);

もう1つの利点は、マクロで配列名を簡単にパラメーター化して、次の情報を取得できることです。

#define NELEMS(x)  (sizeof(x) / sizeof((x)[0]))

int a[17];
size_t n = NELEMS(a);
Elideb picture
2012年04月27日
854

sizeof方法は、パラメーターとして受け取られていない配列を処理する場合の正しい方法です。 関数にパラメーターとして送信された配列はポインターとして扱われるため、 sizeofは、配列のサイズではなく、ポインターのサイズを返します。

したがって、関数内ではこのメソッドは機能しません。 代わりに、配列内の要素の数を示す追加のパラメーターsize_t sizeを常に渡します。

テスト:

#include <stdio.h>
#include <stdlib.h>

void printSizeOf(int intArray[]);
void printLength(int intArray[]);

int main(int argc, char* argv[])
{
    int array[] = { 0, 1, 2, 3, 4, 5, 6 };

    printf("sizeof of array: %d\n", (int) sizeof(array));
    printSizeOf(array);

    printf("Length of array: %d\n", (int)( sizeof(array) / sizeof(array[0]) ));
    printLength(array);
}

void printSizeOf(int intArray[])
{
    printf("sizeof of parameter: %d\n", (int) sizeof(intArray));
}

void printLength(int intArray[])
{
    printf("Length of parameter: %d\n", (int)( sizeof(intArray) / sizeof(intArray[0]) ));
}

出力(64ビットLinux OSの場合):

sizeof of array: 28
sizeof of parameter: 8
Length of array: 7
Length of parameter: 2

出力(32ビットWindows OSの場合):

sizeof of array: 28
sizeof of parameter: 4
Length of array: 7
Length of parameter: 1
Magnus Hoff picture
2008年09月01日
142

sizeofは、ポインターに減衰した配列値を処理する場合には役に立たないことに注意してください。配列の先頭を指していても、コンパイラーにとっては、へのポインターと同じです。その配列の単一の要素。 ポインタは、それを初期化するために使用された配列について他に何も「記憶」しません。

int a[10];
int* p = a;

assert(sizeof(a) / sizeof(a[0]) == 10);
assert(sizeof(p) == sizeof(int*));
assert(sizeof(*p) == sizeof(int));
unwind picture
2008年10月15日
51

「トリック」のサイズは私が知っている最良の方法であり、括弧の使用に1つの小さな、しかし(私にとって、これは主要なペットピーブです)重要な変更があります。

ウィキペディアのエントリが明らかにしているように、Cのsizeofは関数ではありません。 それは演算子です。 したがって、引数が型名でない限り、引数を括弧で囲む必要はありません。 これは、引数が括弧も使用するキャスト式のように見えるため、覚えやすいです。

だから:あなたが以下を持っている場合:

int myArray[10];

次のようなコードで要素の数を見つけることができます:

size_t n = sizeof myArray / sizeof *myArray;

それは、私にとって、括弧付きの代替案よりもはるかに読みやすいです。 また、インデックスよりも簡潔であるため、区分の右側にあるアスタリスクの使用を好みます。

もちろん、これもすべてコンパイル時なので、分割がプログラムのパフォーマンスに影響を与えることを心配する必要はありません。 したがって、可能な限りこのフォームを使用してください。

タイプではなく、実際のオブジェクトがある場合は、sizeofを使用するのが常に最善です。そうすれば、エラーを作成したり、間違ったタイプを指定したりすることを心配する必要がなくなります。

たとえば、ネットワーク全体など、一部のデータをバイトストリームとして出力する関数があるとします。 関数send()呼び出して、送信するオブジェクトへのポインタとオブジェクトのバイト数を引数として取るようにします。 したがって、プロトタイプは次のようになります。

void send(const void *object, size_t size);

次に、整数を送信する必要があるため、次のようにコーディングします。

int foo = 4711;
send(&foo, sizeof (int));

これで、2か所でfooのタイプを指定することにより、足で自分を撃つ微妙な方法を紹介しました。 一方が変更され、もう一方が変更されない場合、コードは壊れます。 したがって、常に次のようにします。

send(&foo, sizeof foo);

今、あなたは保護されています。 確かに、変数の名前を複製しますが、変数を変更すると、コンパイラーが検出できる方法で破損する可能性が高くなります。

Arjun Sreedharan picture
2013年12月08日
38
int size = (&arr)[1] - arr;

説明のためにこのリンクをチェックし

Mohd Shibli picture
2017年05月27日
26

sizeof演算子を使用できますが、ポインターの参照を取得するため、関数では機能しません。配列の長さを見つけるには、次の操作を実行できます。

len = sizeof(arr)/sizeof(arr[0])

ここで最初に見つかったコード:配列内の要素の数を見つけるCプログラム

Abhitesh khatri picture
2014年01月19日
22

配列のデータ型がわかっている場合は、次のようなものを使用できます。

int arr[] = {23, 12, 423, 43, 21, 43, 65, 76, 22};

int noofele = sizeof(arr)/sizeof(int);

または、配列のデータ型がわからない場合は、次のようなものを使用できます。

noofele = sizeof(arr)/sizeof(arr[0]);

注:これは、配列が実行時に定義されておらず(mallocなど)、配列が関数に渡されない場合にのみ機能します。 どちらの場合も、 arr (配列名)はポインターです。

user2379628 picture
2014年02月28日
20

誰もが利用しているマクロARRAYELEMENTCOUNT(x)正しく評価され

/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x[0]))

ARRAYELEMENTCOUNT(p + 1);

実際には次のように評価されます。

(sizeof (p + 1) / sizeof (p + 1[0]));

一方、

/* Compile as: CL /P "macro.c" */
# define ARRAYELEMENTCOUNT(x) (sizeof (x) / sizeof (x)[0])

ARRAYELEMENTCOUNT(p + 1);

正しく評価されます:

(sizeof (p + 1) / sizeof (p + 1)[0]);

これは、実際には配列のサイズとはあまり関係がありません。 Cプリプロセッサがどのように機能するかを実際に観察していないために多くのエラーに気づきました。 の式が関与するのではなく、常にマクロパラメータをラップします。


これは正しいです; 私の例は悪いものでした。 しかし、それは実際に起こるべきことです。 前に述べたように、 p + 1は最終的にポインター型になり、マクロ全体を無効にします(ポインターパラメーターを使用して関数でマクロを使用しようとした場合と同じです)。

結局のところ、この特定の例では、「配列」タイプの式がないため、障害は実際には問題ではありません(したがって、私はみんなの時間を無駄にしているだけです。万歳!)。 しかし、実際にはプリプロセッサの評価に関する微妙な点は重要だと思います。

Andreas Spindler picture
2011年08月17日
17

多次元配列の場合、少し複雑になります。 多くの場合、人々は明示的なマクロ定数を定義します。

#define g_rgDialogRows   2
#define g_rgDialogCols   7

static char const* g_rgDialog[g_rgDialogRows][g_rgDialogCols] =
{
    { " ",  " ",    " ",    " 494", " 210", " Generic Sample Dialog", " " },
    { " 1", " 330", " 174", " 88",  " ",    " OK",        " " },
};

ただし、これらの定数は、コンパイル時にsizeof :を使用して評価することもできます。

#define rows_of_array(name)       \
    (sizeof(name   ) / sizeof(name[0][0]) / columns_of_array(name))
#define columns_of_array(name)    \
    (sizeof(name[0]) / sizeof(name[0][0]))

static char* g_rgDialog[][7] = { /* ... */ };

assert(   rows_of_array(g_rgDialog) == 2);
assert(columns_of_array(g_rgDialog) == 7);

このコードはCおよびC ++で機能することに注意してください。 2次元以上の配列の場合は

sizeof(name[0][0][0])
sizeof(name[0][0][0][0])

など、無限に。

Cacahuete Frito picture
2019年08月17日
17

ここで示す最後の2つのケースである、要素数またはバイト数のいずれかで、配列の2つの異なるサイズのいずれかを取得するためにsizeof使用しないことをお勧めします(使用できる場合でも)。 。 その理由は、コードの意図をメンテナに明らかにし、 sizeof(ptr)sizeof(arr)違いを一目で明らかにするためです(このように書かれていることは明らかではありません)。コードを読んでいる全員。


TL; DR:

#define ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]) + must_be_array(arr))

#define ARRAY_SSIZE(arr)    ((ptrdiff_t)ARRAY_SIZE(arr))

#define ARRAY_BYTES(arr)    (sizeof(arr) + must_be_array(arr))

#define ARRAY_SBYTES(arr)   ((ssize_t)ARRAY_BYTES(arr))

must_be_array(arr) -Wsizeof-pointer-divはバグがあるため(2020年4月現在)、 must_be_array(arr) (以下に定義)が必要です。

#define is_same_type(a, b)  __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr)       (!is_same_type((arr), &(arr)[0]))
#define must_be(e, ...)     (                               \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        _Static_assert((e)  __VA_OPT__(,)  __VA_ARGS__);\
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)
#define must_be_array(arr)  must_be(is_array(arr), "Not a `[]` !")

このトピックに関して重要なバグがありました: https

Linusが提供するソリューション、つまり関数のパラメーターに配列表記を使用しないというソリューションには同意しません。

ポインタが配列として使用されていることを示すドキュメントとして、配列表記が好きです。 しかし、それは、バグのあるコードを書くことが不可能になるように、絶対確実なソリューションを適用する必要があることを意味します。

配列から、知りたいと思うかもしれない3つのサイズがあります。

  • 配列の要素のサイズ
  • 配列内の要素の数
  • 配列がメモリで使用するバイト単位のサイズ

配列の要素のサイズ

最初のものは非常に単純で、同じ方法で行われるため、配列とポインターのどちらを扱っているかは関係ありません。

使用例:

void foo(ptrdiff_t nmemb, int arr[static nmemb])
{
        qsort(arr, nmemb, sizeof(arr[0]), cmp);
}

qsort()は、3番目の引数としてこの値を必要とします。


質問のトピックである他の2つのサイズについては、配列を処理していることを確認し、そうでない場合はコンパイルを中断します。ポインターを処理していると、間違った値が取得されるためです。 。 コンパイルが壊れていると、配列ではなくポインタを扱っていることが簡単にわかります。変数またはマクロのサイズを格納するコードを記述するだけです。ポインタの後ろの配列。


配列内の要素の数

これは最も一般的であり、多くの回答が典型的なマクロARRAY_SIZEを提供しています。

#define ARRAY_SIZE(arr)     (sizeof(arr) / sizeof((arr)[0]))

ARRAY_SIZEの結果は、タイプptrdiff_t符号付き変数で一般的に使用されるため、このマクロの符号付きバリアントを定義することをお勧めします。

#define ARRAY_SSIZE(arr)    ((ptrdiff_t)ARRAY_SIZE(arr))

PTRDIFF_MAXを超えるメンバーを持つ配列は、この署名されたバージョンのマクロに対して無効な値を与えることになりますが、C17 :: 6.5.6.9を読むと、そのような配列はすでに火遊びをしています。 このような場合は、 ARRAY_SIZEsize_tを使用する必要があります。

GCC 8などの最近のバージョンのコンパイラーは、このマクロをポインターに適用すると警告を表示するため、安全です(古いコンパイラーで安全にする方法は他にもあります)。

これは、配列全体のバイト単位のサイズを各要素のサイズで割ることによって機能します。

使用例:

void foo(ptrdiff_t nmemb)
{
        char buf[nmemb];

        fgets(buf, ARRAY_SIZE(buf), stdin);
}

void bar(ptrdiff_t nmemb)
{
        int arr[nmemb];

        for (ptrdiff_t i = 0; i < ARRAY_SSIZE(arr); i++)
                arr[i] = i;
}

これらの関数が配列を使用せず、代わりにパラメーターとして取得した場合、前のコードはコンパイルされないため、バグを発生させることはできません(最近のコンパイラバージョンが使用されているか、他のトリックが使用されている場合)。 、マクロ呼び出しを次の値に置き換える必要があります。

void foo(ptrdiff_t nmemb, char buf[nmemb])
{

        fgets(buf, nmemb, stdin);
}

void bar(ptrdiff_t nmemb, int arr[nmemb])
{

        for (ptrdiff_t i = 0; i < nmemb; i++)
                arr[i] = i;
}

配列がメモリで使用するバイト単位のサイズ

ARRAY_SIZEは、前のケースの解決策として一般的に使用されますが、このケースが安全に記述されることはめったにありません。おそらく、あまり一般的ではないためです。

この値を取得する一般的な方法は、 sizeof(arr)を使用することです。 問題:前のものと同じ。 配列の代わりにポインタがある場合、プログラムはおかしくなります。

この問題の解決策には、以前と同じマクロを使用することが含まれます。これは安全であることがわかっています(ポインターに適用するとコンパイルが中断されます)。

#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))

ARRAY_BYTESの結果が、 ssize_tを返す関数の出力と比較されることがあることを考えると、このマクロの符号付きバリアントを定義することをお勧めします。

#define ARRAY_SBYTES(arr)   ((ssize_t)ARRAY_BYTES(arr))

仕組みは非常に単純です。 ARRAY_SIZEが行う除算を元に戻すため、数学的なキャンセルの後、 sizeof(arr)が1つだけになりますが、 ARRAY_SIZE安全性が追加されます建設。

使用例:

void foo(ptrdiff_t nmemb)
{
        int arr[nmemb];

        memset(arr, 0, ARRAY_BYTES(arr));
}

memset()は、3番目の引数としてこの値を必要とします。

以前と同様に、配列がパラメーター(ポインター)として受信された場合、配列はコンパイルされないため、マクロ呼び出しを次の値に置き換える必要があります。

void foo(ptrdiff_t nmemb, int arr[nmemb])
{

        memset(arr, 0, sizeof(arr[0]) * nmemb);
}

更新(23 / apr / 2020): -Wsizeof-pointer-divはバグがあります:

今日、GCCの新しい警告は、マクロがシステムヘッダーではないヘッダーで定義されている場合にのみ機能することがわかりました。 システムにインストールされているヘッダー(通常は/usr/local/include/または/usr/include/ )( #include <foo.h> )でマクロを定義すると、コンパイラーは警告を発行しません(GCCを試しました) 9.3.0)。

だから私たちは#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))を持っていて、それを安全にしたいのです。 C11 _Static_assert()といくつかのGCC拡張機能が必要になります:式のステートメントと宣言__ builtin_types_compatible_p

#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr)           (!is_same_type((arr), &(arr)[0]))
#define Static_assert_array(arr) _Static_assert(is_array(arr), "Not a `[]` !")

#define ARRAY_SIZE(arr)         (                                       \
{                                                                       \
        Static_assert_array(arr);                                       \
        sizeof(arr) / sizeof((arr)[0]);                                \
}                                                                       \
)

これでARRAY_SIZE()は完全に安全になり、したがってその派生物はすべて安全になります。


更新:libbsdは__arraycount()提供します:

Libbsd<sys/cdefs.h>にマクロ__arraycount()を提供します。これは、括弧のペアがないため安全ではありませんが、それらの括弧を自分で追加できるため、で除算を記述する必要もありません。ヘッダー(既存のコードを複製するのはなぜですか?) そのマクロはシステムヘッダーで定義されているため、それを使用すると、上記のマクロを使用する必要があります。

#include <stddef.h>
#include <sys/cdefs.h>
#include <sys/types.h>


#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr)           (!is_same_type((arr), &(arr)[0]))
#define Static_assert_array(arr) _Static_assert(is_array(arr), "Not a `[]` !")

#define ARRAY_SIZE(arr)         (                                       \
{                                                                       \
        Static_assert_array(arr);                                       \
        __arraycount((arr));                                            \
}                                                                       \
)

#define ARRAY_SSIZE(arr)        ((ptrdiff_t)ARRAY_SIZE(arr))
#define ARRAY_BYTES(arr)        (sizeof((arr)[0]) * ARRAY_SIZE(arr))
#define ARRAY_SBYTES(arr)       ((ssize_t)ARRAY_BYTES(arr))

代わりに<sys/param.h> nitems()を提供するシステムもあれば、両方を提供するシステムもあります。 あなたはあなたのシステムをチェックし、あなたが持っているものを使うべきです、そして多分移植性のためにいくつかのプリプロセッサ条件を使いそして両方をサポートするべきです。


更新:マクロをファイルスコープで使用できるようにします。

残念ながら、 ({}) gcc拡張子はファイルスコープでは使用できません。 ファイルスコープでマクロを使用できるようにするには、静的アサーションがsizeof(struct {})内にある必要があります。 次に、結果に影響を与えないように、 0を掛けます。 (int)へのキャストは、 (int)0を返す関数をシミュレートするのに適している場合があります(この場合は必要ありませんが、他の目的で再利用できます)。

さらに、 ARRAY_BYTES()の定義を少し簡略化できます。

#include <stddef.h>
#include <sys/cdefs.h>
#include <sys/types.h>


#define is_same_type(a, b)     __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(arr)          (!is_same_type((arr), &(arr)[0]))
#define must_be(e, ...)        (                                        \
        0 * (int)sizeof(                                                \
                struct {                                                \
                        _Static_assert((e)  __VA_OPT__(,)  __VA_ARGS__);\
                        char ISO_C_forbids_a_struct_with_no_members__;  \
                }                                                       \
        )                                                               \
)
#define must_be_array(arr)      must_be(is_array(arr), "Not a `[]` !")

#define ARRAY_SIZE(arr)         (__arraycount((arr)) + must_be_array(arr))
#define ARRAY_SSIZE(arr)        ((ptrdiff_t)ARRAY_SIZE(arr))
#define ARRAY_BYTES(arr)        (sizeof(arr) + must_be_array(arr))
#define ARRAY_SBYTES(arr)       ((ssize_t)ARRAY_BYTES(arr))

ノート:

このコードは、完全に必要な次の拡張機能を利用しており、安全性を実現するためにそれらの存在が絶対に必要です。 コンパイラにそれらまたは同様のものがない場合、このレベルの安全性を達成することはできません。

次のC11機能も利用しています。 ただし、古い標準を使用することでその欠如を克服するには、いくつかの汚いトリックを使用します(たとえば、Cコードの「:-!!」とは何ですか? )。

私も次の拡張機能を使用していますが、同じことを行う標準のCの方法があります。