'sizeof'(配列を指すポインター)を見つける方法は?


309

まず、ここにいくつかのコードがあります:

int main() 
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", sizeof(days));
    printf("%u\n", sizeof(ptr));

    return 0;
}

ptr(32ビットシステムでは4バイトであるサイズを指定する代わりに)指している配列のサイズを確認する方法はありますか?


84
私はいつもsizeofで括弧を使用してきました-それが関数呼び出しのように見えることを確認してください、しかし私はそれがより明確だと思います。
ポールトンブリン

20
何故なの?余分な括弧に対して何かありますか?自分で読んだ方が少し読みやすいと思います。
David Thornley、

6
@Paul:まあ..その呼び出しの左側がintへのポインタであると仮定して、それをint * ptr = malloc(4 * sizeof * ptr);と記述します。これは私にははるかに明確です。数学のように、読む括弧が少なく、文字通りの定数を前面に表示します。
アンワインド

4
@unwind-intの配列を意味する場合は、ポインタの配列を割り当てないでください。
ポールトンブリン

6
ここには「配列を指すポインター」はありません。intを指すポインタだけです。
newacct 2013

回答:


269

いいえ、できません。コンパイラーは、ポインターが何を指しているのか知りません。既知の帯域外の値で配列を終了し、その値までサイズをカウントするようなトリックがありますが、それはを使用していませんsizeof()

もう1つのトリックは、Zanが言及したもので、サイズをどこかに隠しておきます。たとえば、配列を動的に割り当てる場合は、必要なブロックより1 int大きいブロックを割り当て、最初のintにサイズを隠しptr+1、配列へのポインタとして返します。サイズが必要な場合は、ポインターをデクリメントして、隠された値をのぞきます。配列だけでなく、ブロック全体を最初から解放することを忘れないでください。


12
コメントの投稿が遅くて申し訳ありませんが、コンパイラーがポインターが何を指しているのかわからない場合、freeはどのくらいのメモリをクリアする必要があるかを知っていますか?私はこの情報が無料で使用できるような機能のために内部に保存されていることを知っています。だから私の質問は、なぜコンパイラもそうできるのですか?
viki.omega9 2013

11
@ viki.omega9、freeは実行時にサイズを検出するため。ランタイム要因(コマンドライン引数、ファイルの内容、月の満ち欠けなど)に応じて配列を異なるサイズにすることができるため、コンパイラーはサイズを知ることができません。
Paul Tomblin 2013

15
簡単なフォローアップ、なぜ無料でサイズを返すことができる関数がないのですか?
viki.omega9 2013

5
まあ、関数がmallocされたメモリでのみ呼び出されることを保証でき、ライブラリがmallocされたメモリを(返されたポインタの前にintを使用して)私が見たほとんどの方法で追跡する場合は、1を記述できます。しかし、ポインタが静的配列などを指している場合、失敗します。同様に、割り当てられたメモリのサイズがプログラムからアクセス可能であるという保証はありません。
Paul Tomblin 2013

9
@ viki.omega9:覚えておくべきもう1つのことは、malloc / freeシステムによって記録されたサイズが、要求したサイズとは異なる場合があることです。あなたは9バイトをmallocして16を取得します。Mallocは3Kバイトで4Kを取得します。または同様の状況。
Zan Lynx 14

85

答えはいいえだ。"

Cプログラマが行うことは、配列のサイズをどこかに格納することです。構造体の一部にすることも、プログラマがmalloc()配列の開始前に長さの値を格納するために、要求されたよりも少し多くのメモリをだますこともできます。


3
これが、pascal文字列の実装方法です
dsm

6
そしてどうやらパスカル文字列は、Excelが非常に速く実行される理由です!
Adam Naylor

8
@アダム:それは速いです。私は私の文字列実装のリストでそれを使用しています。ロードサイズ、プリフェッチpos + size、サイズを検索サイズと比較し、strncmpが等しい場合は、次の文字列に移動して繰り返すため、線形検索に比べて超高速です。約500文字列までのバイナリ検索より高速です。
Zan Lynx

47

動的配列(mallocまたはC ++ new)の場合、他の人が述べたように配列のサイズを格納するか、追加、削除、カウントなどを処理する配列マネージャー構造を構築する必要があります。残念ながら、Cはこれをほぼ同様に実行しませんC ++は、基本的に、格納する配列の種類ごとにビルドする必要があるため、管理する必要のある配列の種類が複数ある場合は面倒です。

例のような静的配列の場合、サイズを取得するために使用される一般的なマクロがありますが、パラメーターが実際に静的配列であるかどうかをチェックしないため、お勧めできません。ただし、マクロは実際のコード、たとえばLinuxカーネルヘッダーで使用されますが、以下のものとは少し異なる場合があります。

#if !defined(ARRAY_SIZE)
    #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
#endif

int main()
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", ARRAY_SIZE(days));
    printf("%u\n", sizeof(ptr));
    return 0;
}

このようなマクロを警戒する必要がある理由でグーグルできます。注意してください。

可能であれば、はるかに安全で使いやすいvectorなどのC ++ stdlib。


11
ARRAY_SIZEは、実際のプログラマーがいたるところで使用している一般的なパラダイムです。
Sanjaya R

5
はい、それは一般的なパラダイムです。忘れてしまい、動的配列で使用するのは簡単ですが、慎重に使用する必要があります。
ライアン、

2
はい、良い点ですが、質問されたのは、静的配列ではなく、ポインターに関するものでした。
Paul Tomblin、

2
ARRAY_SIZE引数が配列(つまり、配列型の式)の場合、そのマクロは常に機能します。いわゆる「動的配列」では、実際の「配列」(配列型の式)を取得することはありません。(もちろん、配列タイプにはコンパイル時のサイズが含まれるため、できません。)最初の要素へのポインターを取得するだけです。「パラメーターが実際に静的配列であるかどうかをチェックしない」という異論は、1つは配列であり、もう1つはそうでないため異なるため、実際には有効ではありません。
newacct 2013

2
同じことをするテンプレート関数が浮かんでいますが、ポインターの使用を妨げます。
Natalie Adams

18

sizeof()を使用せずに、C ++テンプレートを使用したクリーンなソリューションがあります。次のgetSize()関数は、静的配列のサイズを返します。

#include <cstddef>

template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

foo_t構造の例を次に示します。

#include <cstddef>

template<typename T, size_t SIZE>
size_t getSize(T (&)[SIZE]) {
    return SIZE;
}

struct foo_t {
    int ball;
};

int main()
{
    foo_t foos3[] = {{1},{2},{3}};
    foo_t foos5[] = {{1},{2},{3},{4},{5}};
    printf("%u\n", getSize(foos3));
    printf("%u\n", getSize(foos5));

    return 0;
}

出力:

3
5

表記を見たことがありませんT (&)[SIZE]。これが何を意味するか説明できますか?また、このコンテキストでconstexprについて言及することもできます。
WorldSEnder 2014年

2
これは、c ++を使用していて、実際に配列型の変数がある場合に便利です。どちらも問題のケースではありません:言語はCであり、OPが配列サイズを取得したいのは単純なポインターです。
Oguk 2014年

このコードは、すべての異なるサイズ/タイプの組み合わせに対して同じコードを再作成することにより、コードの肥大化につながるのでしょうか、それとも、コンパイラーが存在しないように魔法のように最適化されていますか?
user2796283

@WorldSEnder:これは、配列型の参照用のC ++構文です(変数名なし、サイズと要素型のみ)。
Peter Cordes

@ user2796283:この関数はコンパイル時に完全に最適化されます。魔法は必要ありません。単一の定義に何も組み合わせているのではなく、単にコンパイル時の定数にインライン化しているだけです。(しかし、デバッグビルドでは、はい、異なる定数を返す個別の関数がたくさんあります。リンカーマジックは、同じ定数を使用する関数をマージする可能性があります。呼び出し元はSIZE、引数として渡されません。これは、関数の定義ですでに知られている。)
Peter Cordes

5

この特定の例では、はい、あります。typedefを使用する場合(下記を参照)。もちろん、このようにすると、ポインタが何を指しているかわかっているので、SIZEOF_DAYSを使用することもできます。

malloc()などによって返される(void *)ポインターがある場合、いいえ、ポインターが指しているデータ構造を判別する方法はないため、そのサイズを判別する方法はありません。

#include <stdio.h>

#define NUM_DAYS 5
typedef int days_t[ NUM_DAYS ];
#define SIZEOF_DAYS ( sizeof( days_t ) )

int main() {
    days_t  days;
    days_t *ptr = &days; 

    printf( "SIZEOF_DAYS:  %u\n", SIZEOF_DAYS  );
    printf( "sizeof(days): %u\n", sizeof(days) );
    printf( "sizeof(*ptr): %u\n", sizeof(*ptr) );
    printf( "sizeof(ptr):  %u\n", sizeof(ptr)  );

    return 0;
} 

出力:

SIZEOF_DAYS:  20
sizeof(days): 20
sizeof(*ptr): 20
sizeof(ptr):  4

5

すべての正しい答えが述べたように、配列の減衰したポインタ値だけからこの情報を取得することはできません。減衰したポインタが関数が受け取った引数である場合、元の配列のサイズは、関数がそのサイズを知るために他の方法で提供される必要があります。

これは、これまでに提供されたものとは異なる提案であり、機能します。代わりに、配列へのポインタを渡します。この提案は、Cがテンプレートや参照をサポートしていないことを除いて、C ++スタイルの提案に似ています。

#define ARRAY_SZ 10

void foo (int (*arr)[ARRAY_SZ]) {
    printf("%u\n", (unsigned)sizeof(*arr)/sizeof(**arr));
}

ただし、この関数は、渡される配列のサイズを正確に把握するように定義されているため、問題に対してはばかげています。そのため、配列でsizeofを使用する必要はほとんどありません。ただし、それは、型の安全性を提供することです。不要なサイズの配列を渡すことが禁止されます。

int x[20];
int y[10];
foo(&x); /* error */
foo(&y); /* ok */

関数が任意のサイズの配列を操作できるようになっている場合は、追加情報として関数にサイズを指定する必要があります。


1
「配列の減衰したポインタ値からのみこの情報を取得することはできません」+1、および回避策を提供します。
最大

4

魔法の解決策はありません。Cは反映言語ではありません。オブジェクトは、それらが何であるかを自動的に認識しません。

しかし、あなたには多くの選択肢があります:

  1. 明らかに、パラメータを追加します
  2. 呼び出しをマクロで囲み、パラメーターを自動的に追加します
  3. より複雑なオブジェクトを使用します。動的配列と配列のサイズを含む構造を定義します。次に、構造体のアドレスを渡します。

オブジェクトはそれらが何であるかを知っています。あなたがサブオブジェクトを指している場合でも、完全なオブジェクトまたは大きなサブオブジェクトに関する情報を取得する方法はありません
MM

2

この問題に対する私の解決策は、配列の長さを配列に関するメタ情報としてstruct Arrayに保存することです。

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

struct Array
{
    int length;

    double *array;
};

typedef struct Array Array;

Array* NewArray(int length)
{
    /* Allocate the memory for the struct Array */
    Array *newArray = (Array*) malloc(sizeof(Array));

    /* Insert only non-negative length's*/
    newArray->length = (length > 0) ? length : 0;

    newArray->array = (double*) malloc(length*sizeof(double));

    return newArray;
}

void SetArray(Array *structure,int length,double* array)
{
    structure->length = length;
    structure->array = array;
}

void PrintArray(Array *structure)
{       
    if(structure->length > 0)
    {
        int i;
        printf("length: %d\n", structure->length);
        for (i = 0; i < structure->length; i++)
            printf("%g\n", structure->array[i]);
    }
    else
        printf("Empty Array. Length 0\n");
}

int main()
{
    int i;
    Array *negativeTest, *days = NewArray(5);

    double moreDays[] = {1,2,3,4,5,6,7,8,9,10};

    for (i = 0; i < days->length; i++)
        days->array[i] = i+1;

    PrintArray(days);

    SetArray(days,10,moreDays);

    PrintArray(days);

    negativeTest = NewArray(-5);

    PrintArray(negativeTest);

    return 0;
}

しかし、あなたが保存したい配列の正しい長さを設定することに注意する必要があります。なぜなら、私たちの友人が大々的に説明したように、この長さをチェックする方法はないからです。


2

あなたはこのようなことをすることができます:

int days[] = { /*length:*/5, /*values:*/ 1,2,3,4,5 };
int *ptr = days + 1;
printf("array length: %u\n", ptr[-1]);
return 0;

1

いいえ、sizeof(ptr)配列のサイズptrが指しているのを見つけるために使用することはできません。

ただし、追加のメモリ(配列のサイズ以上)を割り当てると、長さを追加のスペースに格納する場合に役立ちます。


1
int main() 
{
    int days[] = {1,2,3,4,5};
    int *ptr = days;
    printf("%u\n", sizeof(days));
    printf("%u\n", sizeof(ptr));

    return 0;
}

days []のサイズは20で、これは要素数*データ型のサイズではありません。ポインタのサイズは4ですが、それが何を指しているかに関係ありません。ポインタは、アドレスを格納することによって他の要素を指すからです。


1
sizeof(PTR)ポインタおよびsizeofのサイズ(* PTR)はポインタの大きされるまで
阿弥陀

0
 #define array_size 10

 struct {
     int16 size;
     int16 array[array_size];
     int16 property1[(array_size/16)+1]
     int16 property2[(array_size/16)+1]
 } array1 = {array_size, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

 #undef array_size

array_sizeがサイズ変数に渡されています:

#define array_size 30

struct {
    int16 size;
    int16 array[array_size];
    int16 property1[(array_size/16)+1]
    int16 property2[(array_size/16)+1]
} array2 = {array_size};

#undef array_size

使い方は:

void main() {

    int16 size = array1.size;
    for (int i=0; i!=size; i++) {

        array1.array[i] *= 2;
    }
}

0

文字列では'\0'、末尾に文字があるため、などの関数を使用して文字列の長さを取得できますstrlen。たとえば、整数配列の問題は、値を終了値として使用できないため、解決策の1つは、配列をアドレス指定し、終了値としてNULLポインターを使用することです。

#include <stdio.h>
/* the following function will produce the warning:
 * ‘sizeof’ on array function parameter ‘a’ will
 * return size of ‘int *’ [-Wsizeof-array-argument]
 */
void foo( int a[] )
{
    printf( "%lu\n", sizeof a );
}
/* so we have to implement something else one possible
 * idea is to use the NULL pointer as a control value
 * the same way '\0' is used in strings but this way
 * the pointer passed to a function should address pointers
 * so the actual implementation of an array type will
 * be a pointer to pointer
 */
typedef char * type_t; /* line 18 */
typedef type_t ** array_t;
int main( void )
{
    array_t initialize( int, ... );
    /* initialize an array with four values "foo", "bar", "baz", "foobar"
     * if one wants to use integers rather than strings than in the typedef
     * declaration at line 18 the char * type should be changed with int
     * and in the format used for printing the array values 
     * at line 45 and 51 "%s" should be changed with "%i"
     */
    array_t array = initialize( 4, "foo", "bar", "baz", "foobar" );

    int size( array_t );
    /* print array size */
    printf( "size %i:\n", size( array ));

    void aprint( char *, array_t );
    /* print array values */
    aprint( "%s\n", array ); /* line 45 */

    type_t getval( array_t, int );
    /* print an indexed value */
    int i = 2;
    type_t val = getval( array, i );
    printf( "%i: %s\n", i, val ); /* line 51 */

    void delete( array_t );
    /* free some space */
    delete( array );

    return 0;
}
/* the output of the program should be:
 * size 4:
 * foo
 * bar
 * baz
 * foobar
 * 2: baz
 */
#include <stdarg.h>
#include <stdlib.h>
array_t initialize( int n, ... )
{
    /* here we store the array values */
    type_t *v = (type_t *) malloc( sizeof( type_t ) * n );
    va_list ap;
    va_start( ap, n );
    int j;
    for ( j = 0; j < n; j++ )
        v[j] = va_arg( ap, type_t );
    va_end( ap );
    /* the actual array will hold the addresses of those
     * values plus a NULL pointer
     */
    array_t a = (array_t) malloc( sizeof( type_t *) * ( n + 1 ));
    a[n] = NULL;
    for ( j = 0; j < n; j++ )
        a[j] = v + j;
    return a;
}
int size( array_t a )
{
    int n = 0;
    while ( *a++ != NULL )
        n++;
    return n;
}
void aprint( char *fmt, array_t a )
{
    while ( *a != NULL )
        printf( fmt, **a++ );   
}
type_t getval( array_t a, int i )
{
    return *a[i];
}
void delete( array_t a )
{
    free( *a );
    free( a );
}

あなたのコードはコメントでいっぱいですが、これがコードの外でどのように機能するかの一般的な説明を通常のテキストとして追加すると、すべてが簡単になると思います。質問を編集し実行できますか?ありがとうございました!
Fabioは、モニカ

各要素へのポインタの配列を作成して、それを線形検索できるようにすることNULLは、個別の要素をsize直接格納するだけでは考えられない最も効率の悪い代替手段です。特に、この追加の間接層を常に実際に使用している場合はなおさらです。
Peter Cordes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.