Bagaimana menemukan 'sizeof' (pointer yang menunjuk ke sebuah array)?

309

Pertama, berikut adalah beberapa kode:

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

    return 0;
}

Apakah ada cara untuk mengetahui ukuran array yang ptrmenunjuk (bukan hanya memberikan ukurannya, yaitu empat byte pada sistem 32-bit)?

jkidv
sumber
84
Saya selalu menggunakan parens dengan sizeof - tentu saja membuatnya tampak seperti panggilan fungsi, tapi saya pikir itu lebih jelas.
Paul Tomblin
20
Kenapa tidak? Apakah Anda memiliki sesuatu terhadap tanda kurung yang berlebihan? Saya pikir itu membaca sedikit lebih mudah dengan mereka, saya sendiri.
David Thornley
6
@ Paul: well .. dengan asumsi sisi kiri panggilan itu adalah pointer ke int, saya akan menuliskannya sebagai int * ptr = malloc (4 * sizeof * ptr); yang bagi saya jauh lebih jelas. Kurang orangtua untuk membaca, dan membawa konstanta literal ke depan, seperti dalam matematika.
bersantai
4
@wwind - jangan mengalokasikan array pointer ketika Anda bermaksud array int!
Paul Tomblin
6
Tidak ada "pointer yang menunjuk ke array" di sini. Hanya sebuah pointer yang menunjuk ke int.
Newacct

Jawaban:

269

Tidak bisa. Compiler tidak tahu apa yang menunjuk pointer. Ada trik, seperti mengakhiri array dengan nilai out-of-band yang diketahui dan kemudian menghitung ukuran hingga nilai itu, tetapi itu tidak menggunakan sizeof().

Trik lain adalah yang disebutkan oleh Zan , yaitu menyembunyikan ukuran di suatu tempat. Misalnya, jika Anda secara dinamis mengalokasikan array, alokasikan satu blok int lebih besar dari yang Anda butuhkan, simpan ukuran di int pertama, dan kembali ptr+1sebagai penunjuk ke array. Saat Anda membutuhkan ukuran, kurangi pointer dan intip pada nilai simpanan. Ingatlah untuk membebaskan seluruh blok mulai dari awal, dan bukan hanya array.

Paul Tomblin
sumber
12
Saya minta maaf karena posting ini komentar yang terlambat, tetapi jika kompiler tidak tahu apa yang ditunjukkan oleh pointer, bagaimana cara bebas mengetahui berapa banyak memori yang harus dihapus? Saya tahu bahwa informasi ini disimpan secara internal untuk fungsi seperti bebas untuk digunakan. Jadi pertanyaan saya adalah mengapa 'kompiler dapat melakukannya juga?
viki.omega9
11
@ viki.omega9, karena gratis menemukan ukuran saat runtime. Kompiler tidak dapat mengetahui ukurannya karena Anda dapat membuat ukuran array berbeda tergantung pada faktor runtime (argumen baris perintah, isi file, fase bulan, dll).
Paul Tomblin
15
Tindak lanjut cepat, mengapa tidak ada fungsi yang dapat mengembalikan ukuran dengan cara yang bebas?
viki.omega9
5
Nah, jika Anda bisa menjamin bahwa fungsi itu hanya dipanggil dengan memori malloced dan perpustakaan melacak memori malloced dengan cara yang paling banyak saya lakukan (dengan menggunakan int sebelum pointer dikembalikan) maka Anda bisa menulis satu. Tetapi jika pointer ke array statis atau sejenisnya, itu akan gagal. Demikian pula, tidak ada jaminan bahwa ukuran memori malloced dapat diakses oleh program Anda.
Paul Tomblin
9
@ viki.omega9: Hal lain yang perlu diingat adalah bahwa ukuran yang direkam oleh malloc / sistem bebas mungkin bukan ukuran yang Anda minta. Anda malloc 9 byte dan dapatkan 16. Malloc 3K byte dan dapatkan 4K. Atau situasi serupa.
Zan Lynx
85

Jawabannya adalah tidak."

Apa yang programmer C lakukan adalah menyimpan ukuran array di suatu tempat. Ini bisa menjadi bagian dari struktur, atau programmer dapat menipu sedikit dan malloc()lebih banyak memori daripada yang diminta untuk menyimpan nilai panjang sebelum dimulainya array.

Zan Lynx
sumber
3
Thats bagaimana string pascal diimplementasikan
dsm
6
dan tampaknya string pascal adalah mengapa excel berjalan sangat cepat!
Adam Naylor
8
@ Adam: Cepat. Saya menggunakannya dalam daftar implementasi string saya. Ini super cepat untuk pencarian linier karena: ukuran beban, ukuran + pos pengambilan awal, bandingkan ukuran dengan ukuran pencarian, jika sama strncmp, pindah ke string berikutnya, ulangi. Ini lebih cepat daripada pencarian biner hingga sekitar 500 string.
Zan Lynx
47

Untuk array dinamis ( malloc atau C ++ baru ), Anda perlu menyimpan ukuran array seperti yang disebutkan oleh orang lain atau mungkin membangun struktur manajer array yang menangani menambah, menghapus, menghitung, dll. Sayangnya C tidak melakukan ini hampir sebaik C ++ karena pada dasarnya Anda harus membangunnya untuk setiap jenis array berbeda yang Anda simpan yang rumit jika Anda memiliki beberapa jenis array yang perlu Anda kelola.

Untuk array statis, seperti yang ada di contoh Anda, ada makro umum yang digunakan untuk mendapatkan ukuran, tetapi tidak disarankan karena tidak memeriksa apakah parameternya benar-benar array statis. Makro digunakan dalam kode nyata, misalnya dalam header kernel Linux meskipun mungkin sedikit berbeda dari yang di bawah ini:

#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;
}

Anda dapat google karena alasan untuk waspada terhadap makro seperti ini. Hati-hati.

Jika memungkinkan, stdlib C ++ seperti vektor yang jauh lebih aman dan lebih mudah digunakan.

Ryan
sumber
11
ARRAY_SIZE adalah paradigma umum yang digunakan oleh programmer praktis di mana saja.
Sanjaya R
5
Ya itu adalah paradigma yang umum. Anda masih perlu menggunakannya dengan hati-hati karena mudah dilupakan dan digunakan pada array dinamis.
Ryan
2
Ya, poin yang bagus, tetapi pertanyaan yang diajukan adalah tentang pointer, bukan array statis.
Paul Tomblin
2
Itu ARRAY_SIZEmakro selalu bekerja jika argumen adalah array (yaitu ekspresi tipe array). Untuk apa yang disebut "array dinamis", Anda tidak pernah mendapatkan "array" yang sebenarnya (ekspresi tipe array). (Tentu saja, Anda tidak bisa, karena tipe array menyertakan ukurannya pada waktu kompilasi.) Anda hanya mendapatkan pointer ke elemen pertama. Keberatan Anda "tidak memeriksa apakah parameternya benar-benar array statis" tidak benar-benar valid, karena mereka berbeda karena satu adalah array dan yang lainnya tidak.
newacct
2
Ada fungsi templat melayang yang melakukan hal yang sama tetapi akan mencegah penggunaan pointer.
Natalie Adams
18

Ada solusi bersih dengan templat C ++, tanpa menggunakan sizeof () . Fungsi getSize () berikut mengembalikan ukuran array statis apa pun:

#include <cstddef>

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

Berikut ini adalah contoh dengan struktur 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;
}

Keluaran:

3
5
Skurton
sumber
Saya belum pernah melihat notasi T (&)[SIZE]. Bisakah Anda menjelaskan apa artinya ini? Anda juga bisa menyebutkan constexpr dalam konteks ini.
WorldSEnder
2
Itu bagus jika Anda menggunakan c ++ dan Anda benar-benar memiliki variabel tipe array. Tak satu pun dari mereka adalah kasus dalam pertanyaan: Bahasa adalah C, dan hal OP ingin mendapatkan ukuran array dari adalah pointer sederhana.
Oguk
akankah kode ini mengarah pada kode mengasapi dengan menciptakan kembali kode yang sama untuk setiap kombinasi ukuran / tipe yang berbeda atau apakah secara ajaib dioptimalkan keberadaannya oleh kompiler?
user2796283
@ WorldSEnder: Itu sintaks C ++ untuk referensi tipe array (tanpa nama variabel, hanya ukuran dan tipe elemen).
Peter Cordes
@ user2796283: Fungsi ini dioptimalkan sepenuhnya pada waktu kompilasi; tidak perlu sihir; itu tidak menggabungkan apa pun ke satu definisi, itu hanya mengurutkannya ke konstanta waktu kompilasi. (Tetapi dalam membangun debug, ya, Anda akan memiliki banyak fungsi terpisah yang mengembalikan konstanta yang berbeda. Magic linker mungkin menggabungkan yang menggunakan konstanta yang sama. Penelepon tidak lulus SIZEsebagai argumen, itu adalah param templat yang memiliki sudah diketahui oleh definisi fungsi.)
Peter Cordes
5

Untuk contoh khusus ini, ya, ada, JIKA Anda menggunakan typedefs (lihat di bawah). Tentu saja, jika Anda melakukannya dengan cara ini, Anda sebaiknya menggunakan SIZEOF_DAYS, karena Anda tahu apa yang ditunjukkan oleh pointer.

Jika Anda memiliki pointer (void *), seperti yang dikembalikan oleh malloc () atau sejenisnya, maka, tidak, tidak ada cara untuk menentukan struktur data apa yang menunjuk pointer dan dengan demikian, tidak ada cara untuk menentukan ukurannya.

#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;
} 

Keluaran:

SIZEOF_DAYS:  20
sizeof(days): 20
sizeof(*ptr): 20
sizeof(ptr):  4
David
sumber
5

Karena semua jawaban yang benar telah dinyatakan, Anda tidak bisa mendapatkan informasi ini hanya dari nilai pointer yang rusak dari array. Jika pembusukan pointer adalah argumen yang diterima oleh fungsi, maka ukuran array yang berasal harus disediakan dalam beberapa cara lain agar fungsi mengetahui ukuran itu.

Berikut adalah saran yang berbeda dari apa yang telah disediakan sejauh ini, yang akan berfungsi: Lewati sebuah pointer ke array. Saran ini mirip dengan saran gaya C ++, kecuali bahwa C tidak mendukung templat atau referensi:

#define ARRAY_SZ 10

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

Tapi, saran ini agak konyol untuk masalah Anda, karena fungsi ini didefinisikan untuk mengetahui persis ukuran array yang dilewatkan (karenanya, ada sedikit kebutuhan untuk menggunakan sizeof sama sekali pada array). Apa yang dilakukannya adalah menawarkan beberapa tipe keamanan. Ini akan melarang Anda untuk melewati array dengan ukuran yang tidak diinginkan.

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

Jika fungsi tersebut seharusnya dapat beroperasi pada berbagai ukuran array, maka Anda harus memberikan ukuran pada fungsi sebagai informasi tambahan.

jxh
sumber
1
+1 untuk "Anda tidak bisa mendapatkan informasi ini hanya dari nilai pointer yang rusak dari array" dan memberikan solusi.
Maks
4

Tidak ada solusi ajaib. C bukan bahasa reflektif. Objek tidak secara otomatis tahu apa itu.

Tetapi Anda memiliki banyak pilihan:

  1. Jelas, tambahkan parameter
  2. Bungkus panggilan dalam makro dan secara otomatis menambahkan parameter
  3. Gunakan objek yang lebih kompleks. Tentukan struktur yang berisi array dinamis dan juga ukuran array. Kemudian, sampaikan alamat struktur.
DigitalRoss
sumber
Objek tahu apa itu. Tetapi jika Anda menunjuk ke sebuah sub-objek, tidak ada cara untuk mendapatkan informasi tentang objek lengkap atau sub
MM
2

Solusi saya untuk masalah ini adalah menyimpan panjang array ke dalam array Array sebagai meta-informasi tentang 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;
}

Tetapi Anda harus peduli mengatur panjang array yang tepat yang ingin Anda simpan, karena tidak ada cara untuk memeriksa panjang ini, seperti yang dijelaskan teman-teman kami secara besar-besaran.


sumber
2

Anda dapat melakukan sesuatu seperti ini:

int days[] = { /*length:*/5, /*values:*/ 1,2,3,4,5 };
int *ptr = days + 1;
printf("array length: %u\n", ptr[-1]);
return 0;
Tᴏᴍᴇʀ Wᴏʟʙᴇʀɢ
sumber
1

Tidak, Anda tidak dapat menggunakan sizeof(ptr)untuk menemukan ukuran array ptryang menunjuk.

Meskipun mengalokasikan memori ekstra (lebih dari ukuran array) akan sangat membantu jika Anda ingin menyimpan panjang di ruang ekstra.

SKD
sumber
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;
}

Ukuran hari [] adalah 20 yang bukan dari elemen * ukuran tipe datanya. Sementara ukuran pointer adalah 4 tidak peduli apa yang menunjuk. Karena sebuah pointer menunjuk ke elemen lain dengan menyimpan alamatnya.

Shivangi Chaurasia
sumber
1
sizeof (ptr) adalah ukuran pointer dan sizeof (* ptr) adalah ukuran pointer yang
Amitābha
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 diteruskan ke variabel ukuran :

#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

Penggunaannya adalah:

void main() {

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

        array1.array[i] *= 2;
    }
}
pengguna3065147
sumber
0

Dalam string ada '\0'karakter di akhir sehingga panjang string bisa didapat menggunakan fungsi seperti strlen. Masalah dengan array integer, misalnya, adalah bahwa Anda tidak dapat menggunakan nilai apa pun sebagai nilai akhir sehingga salah satu solusi yang mungkin adalah mengatasi array dan menggunakan nilai akhir sebagai NULLpenunjuk.

#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 );
}
baz
sumber
Kode Anda penuh dengan komentar, tetapi saya pikir itu akan membuat semuanya lebih mudah jika Anda menambahkan beberapa penjelasan umum tentang cara kerjanya di luar kode, seperti teks biasa. Bisakah Anda mengedit pertanyaan Anda dan melakukannya? Terima kasih!
Fabio mengatakan Reinstate Monica
Membuat array pointer ke setiap elemen sehingga Anda dapat mencari-linearnya NULLmungkin adalah alternatif yang paling efisien yang bisa dibayangkan dengan hanya menyimpan yang terpisah sizesecara langsung. Terutama jika Anda benar-benar menggunakan lapisan tipuan ekstra ini sepanjang waktu.
Peter Cordes