C dizimin boyutunu nasıl belirlerim?


Yanıtlar:


1260

Yönetici Özeti:

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

Tam cevap:

Dizinizin bayt cinsinden boyutunu belirlemek için sizeof işleci kullanabilirsiniz :

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

Bilgisayarımda ints 4 bayt uzunluğunda, n ise 68.

Dizideki öğelerin sayısını belirlemek için dizinin toplam boyutunu dizi öğesinin boyutuna bölebiliriz. Bunu tür ile yapabilirsiniz, şöyle:

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

ve uygun cevabı alın (68/4 = 17), fakat eğer adeğiştirilirse değiştirmeyi unutursanız kötü bir hataya sahip olursunuz sizeof(int).

Dolayısıyla, tercih edilen bölen sizeof(a[0])ya da eşdeğerdir sizeof(*a), dizinin ilk elemanının boyutudur.

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

Başka bir avantaj, bir makrodaki dizi adını kolayca parametreleştirebilmeniz ve şunları elde edebilmenizdir:

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

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

6
Derleyici derleme zamanında * int_arr türünü (ve dolayısıyla sizeof (* int_arr)) değerini bildiğinden, oluşturulan kod aynı olacaktır. Bu bir sabit olacak ve derleyici buna göre optimizasyon yapabilir.
Mark Harrison

10
Sizeof'in sonuçları derleme zamanı sabiti olarak tanımlandığından, tüm derleyicilerde durum böyle olmalıdır.
Mark Harrison

451
Önemli : Burada okumayı bırakmayın, bir sonraki cevabı okuyun! Bu yalnızca yığındaki diziler için geçerlidir , örneğin malloc () kullanıyorsanız veya bir işlev parametresine erişiyorsanız, şansınız kalmaz. Aşağıya bakınız.
Markus

7
C veya C ++ 'da Windows API programlama için ARRAYSIZEtanımlanmış makro vardır WinNT.h(diğer başlıklar tarafından içeri alınır). Bu yüzden WinAPI kullanıcılarının kendi makrolarını tanımlamaları gerekmez.
Lumi

17
@Markus bir dizi türüne sahip herhangi bir değişken için çalışır; bunun "yığın üzerinde" olması gerekmez. Örn static int a[20];. Ancak yorumunuz, bir dizi ile işaretçi arasındaki farkı fark etmeyebilecek okuyucular için kullanışlıdır.
MM

808

sizeofYolu doğru yoldur IFF Eğer parametre olarak alınmayan diziler ile ilgileniyor. Bir işleve parametre olarak gönderilen bir dizi işaretçi olarak kabul edilir, bu nedenle sizeofdizinin yerine işaretçinin boyutunu döndürür.

Böylece, fonksiyonların içinde bu yöntem çalışmaz. Bunun yerine, her zaman size_t sizedizideki öğe sayısını belirten ek bir parametre iletin.

Ölçek:

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

Çıktı (64 bit Linux işletim sisteminde):

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

Çıkış (32 bit Windows işletim sisteminde):

sizeof of array: 28
sizeof of parameter: 4
Length of array: 7
Length of parameter: 1

10
neden length of parameter:2sadece 1. dizi elemanına bir işaretçi geçilirse?
Bbvarghe

16
@Bbvarghe 64bit sistemlerde işaretçiler 8 bayt (sizeof (intArray)), ancak ints hala (genellikle) 4 bayt uzunluğundadır (sizeof (intArray [0])).
Elideb

13
@Pacerier: Doğru kod yoktur - genel çözüm, uzunluğu diziyle birlikte ayrı bir argüman olarak iletmektir.
Jean Hominal

10
Bekle, böylece diziye doğrudan bir işaretçiden erişmenin ve boyutunu görmenin bir yolu yok mu? Burada C'de yeni.
sudo

7
@Michael Trouw: iyi hissetmeni markaları olup olmadığını operatör sözdizimi kullanabilirsiniz: (sizeof array / sizeof *array).
chqrlie

134

sizeofBir işaretçiye çürümüş bir dizi değeri ile uğraşırken yardımcı olmadığını belirtmek gerekir : bir dizinin başlangıcını göstermesine rağmen, derleyiciye o dizinin tek bir öğesinin işaretçisi ile aynıdır . Bir işaretçi, diziyi başlatmak için kullanılan dizi hakkında başka bir şey "hatırlamaz".

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

assert(sizeof(a) / sizeof(a[0]) == 10);
assert(sizeof(p) == sizeof(int*));
assert(sizeof(*p) == sizeof(int));

1
@ Magnus: Standart sizeof'i nesnedeki bayt sayısını verir ve sizeof (char) daima birdir. Bir bayttaki bit sayısı uygulamaya özgüdür. Düzenleme: ANSI C ++ standart bölüm 5.3.3 Sizeof: "sizeof operatörü kendi işlenen nesne gösterimi bayt sayısını verir. [...] sizeof (char), sizeof (işaretli char) ve sizeof (unsigned char) vardır 1; diğer herhangi bir temel türe uygulanan sizeof sonucu uygulama tarafından tanımlanır. "
Skizz

Bölüm 1.6 C ++ bellek modeli: "C ++ bellek modelindeki temel depolama birimi bayttır. Bir bayt, en azından temel yürütme karakter kümesinin herhangi bir üyesini içerecek kadar büyüktür ve bitişik bir bit dizisinden oluşur, sayı bunlardan biri uygulama tanımlı. "
Skizz

2
CRAY'ın char32 bitlik C'ye sahip olduğunu hatırlıyorum . Tüm standart, 0 ile 127 arasındaki tamsayı değerlerinin temsil edilebileceği ve aralığının en az -127 ile 127 (karakter imzalı) veya 0 ile 255 (karakter imzasız) olabileceğidir.
vonbrand

5
Bu mükemmel bir yanıttır. Yukarıdaki tüm iddiaların DOĞRU olarak değerlendirildiğini söylemek istiyorum.
Javad

49

"Hile" boyutu, bildiğim en iyi yol, bir küçük ama (bana göre, bu büyük bir evcil hayvan huşu) parantez kullanımında önemli bir değişiklik.

Wikipedia girişinin de belirttiği gibi, C'ler sizeofbir işlev değildir; bir operatör . Bu nedenle, argüman bir tür adı olmadığı sürece argümanının etrafında parantez gerektirmez. Bunu hatırlamak kolaydır, çünkü argümanı parantez kullanan döküm ifadesine benzetir.

Yani: Aşağıdakilere sahipseniz:

int myArray[10];

Kodu olan öğelerin sayısını şu şekilde bulabilirsiniz:

size_t n = sizeof myArray / sizeof *myArray;

Bu, bana göre, parantez alternatifinden çok daha kolay okur. Ayrıca, yıldızın dizinin sağ tarafında kullanılmasını tercih ediyorum, çünkü endekslemeden daha özlü.

Tabii ki, bu da derleme zamanıdır, bu nedenle programın performansını etkileyen bölüm hakkında endişelenmenize gerek yoktur. Yani bu formu mümkün olan her yerde kullanın.

Bir tür yerine, bir nesneye sahip olduğunuzda gerçek bir nesnede sizeof kullanmak her zaman en iyisidir, çünkü o zaman bir hata yapma ve yanlış türü belirleme konusunda endişelenmenize gerek yoktur.

Örneğin, örneğin bir ağ üzerinden bazı verileri bayt akışı olarak çıkaran bir işleviniz olduğunu varsayalım. İşlevi çağıralım ve send()argüman olarak gönderilecek nesneye bir işaretçi ve nesnedeki bayt sayısını almasını sağlayalım. Böylece, prototip şöyle olur:

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

Ve sonra bir tamsayı göndermeniz gerekir, böylece bunu şöyle kodlayabilirsiniz:

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

Şimdi, fooiki yerde türünü belirterek kendinizi ayağa vurmanın ince bir yolunu tanıttınız . Biri değişir ancak diğeri değişmezse kod kırılır. Böylece, her zaman böyle yapın:

send(&foo, sizeof foo);

Artık korunuyorsunuz. Elbette, değişkenin adını çoğaltırsınız, ancak değiştirirseniz derleyicinin algılayabileceği şekilde kırılma olasılığı yüksektir.


Btw, işlemci seviyesinde aynı talimatlar mı? Daha sizeof(int)az talimat gerektirir sizeof(foo)mi?
Pacerier

@ Pacer: hayır, aynılar. int x = 1+1;Karşı düşün int x = (1+1);. Burada parantezler kesinlikle sadece estetiktir.
quetzalcoatl

@Aidiakapi Bu doğru değil, C99 VLA'ları düşünün.
açma

@ unwind Teşekkürler, düzeltilmiş duruyorum. Yorumumu düzeltmek için sizeofC ++ ve C89'da her zaman sabit olacak. C99'un değişken uzunluklu dizileri ile çalışma zamanında değerlendirilebilir.
Aidiakapi

2
sizeofoperatör olabilir ancak Linus Torvalds'a göre bir işlev olarak ele alınmalıdır. Katılıyorum. Rasyonelini buradan okuyun: lkml.org/lkml/2012/7/11/103
Dr. Person Person II

37
int size = (&arr)[1] - arr;

Açıklama için bu bağlantıya göz atın


7
Küçük nitpick: işaretçi çıkarma sonucunun türü vardır ptrdiff_t. (Genellikle 64 bit sistemde, bu daha büyük bir tür olacaktır int). Değiştirmek bile intiçin ptrdiff_tbu kodda, yine bir hata var arradres alanının yarısından fazlasını kaplar.
MM

2
@MM Başka bir küçük nitpick: Sistem mimarinize bağlı olarak, adres alanı çoğu sistemdeki işaretçi boyutu kadar büyük değildir. Örneğin Windows, 64 bit uygulamalar için adres alanını 8 TB veya 44 bit ile sınırlar. Dolayısıyla, örneğin 4.1TB adres alanınızın yarısından daha büyük bir diziniz olsa bile, bu bir hata olmayacaktır. Yalnızca adres alanınız bu sistemlerde 63 biti aşarsa, bu tür bir hatayla bile karşılaşabilirsiniz. Genel olarak, endişelenme.
Aidiakapi

1
@Aidiakapi, 32-bit x86 Linux veya Windows'ta /3Gisteğe bağlı olarak, adres alanı boyutunun% 75'ine kadar diziler boyutuna sahip olmanızı sağlayan 3G / 1G kullanıcı / çekirdek bölünmesine sahiptir.
Ruslan

1
foo buf1[80]; foo buf2[sizeof buf1/sizeof buf1[0]]; foo buf3[(&buf1)[1] - buf1];Global değişkenler olarak düşünün . sabit olmadığı için buf3[]bildirim başarısız (&buf1)[1] - buf1olur.
chux - Monica'yı eski haline getir

2
Bu, teknik olarak tanımlanmamış bir davranıştır, çünkü standart bir dizinin sonundan sonra silme işlemini açıkça engeller (depolanan değeri okumaya çalışmasanız bile)
MM


21

Dizinin veri türünü biliyorsanız, şöyle bir şey kullanabilirsiniz:

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

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

Veya dizinin veri türünü bilmiyorsanız, aşağıdaki gibi bir şey kullanabilirsiniz:

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

Not: Bu şey yalnızca dizi çalışma zamanında tanımlanmadıysa (malloc gibi) ve dizi bir işlevde geçmediğinde çalışır. Her iki durumda da arr(dizi adı) bir göstericidir.


4
int noofele = sizeof(arr)/sizeof(int);kodlamadan sadece yarı yol daha iyidir int noofele = 9;. Kullanılması sizeof(arr)esneklik dizi boyutunda değişiklik gerektiğini korur. Yine sizeof(int)de, arr[]değişiklik türünün güncellenmesi gerekir . sizeof(arr)/sizeof(arr[0])Türü iyi bilinse bile kullanmak daha iyidir. Neden vs. intiçin kullanıldığını , döndüren türü açık değil . noofelesize_tsizeof()
chux - Monica'yı

19

ARRAYELEMENTCOUNT(x)Herkesin kullandığı makro yanlış değerlendirilir . Bu, gerçekçi bir şekilde, sadece hassas bir konudur, çünkü 'dizi' türüyle sonuçlanan ifadelere sahip olamazsınız.

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

ARRAYELEMENTCOUNT(p + 1);

Aslında şu şekilde değerlendirir:

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

Buna karşılık

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

ARRAYELEMENTCOUNT(p + 1);

Aşağıdakileri doğru bir şekilde değerlendirir:

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

Bunun gerçekten dizilerin boyutu ile açıkça ilgisi yoktur. C ön işlemcisinin nasıl çalıştığını gerçekten gözlemlememekten kaynaklanan birçok hata fark ettim. İçinde olabilecek bir ifade değil, her zaman makro parametresini kaydırırsınız.


Doğru; benim örneğim kötü bir örnekti. Ama aslında tam olarak ne olması gerekiyor. Daha önce de belirttiğim p + 1gibi, bir işaretçi türü olarak sona erecek ve tüm makroyu geçersiz kılacaktır (makroyu bir pointer parametresine sahip bir işlevde kullanmaya çalıştığınız gibi).

Günün sonunda, bu özel durumda, hata gerçekten önemli değil (bu yüzden sadece herkesin zamanını boşa harcıyorum; huzzah!), Çünkü bir tür 'dizi' ile ifadeleriniz yok. Ama gerçekten de önişlemci değerlendirmesiyle ilgili önemli nokta bence önemli.


2
Açıklama için teşekkürler. Orijinal sürüm, derleme zamanı hatasına neden olur. Clang raporları "abone değeri bir dizi, işaretçi veya vektör değil". Makrolarda değerlendirme sırası hakkındaki yorumlarınız iyi alınmasına rağmen, bu örnekte tercih edilen davranış budur.
Mark Harrison

1
Derleyici şikayetini yanlış türde bir otomatik bildirim olarak düşünmemiştim. Teşekkür ederim!

3
Kullanmamanın bir nedeni var mı (sizeof (x) / sizeof (*x))?
seriousdev

16

Çok boyutlu diziler için biraz daha karmaşıktır. Genellikle insanlar açık makro sabitleri tanımlar, yani

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

Ancak bu sabitler, derleme zamanında sizeof ile de değerlendirilebilir :

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

Bu kod C ve C ++ ile çalıştığını unutmayın. İkiden fazla boyutu olan diziler için

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

vb. reklam sonsuz.


15
sizeof(array) / sizeof(array[0])

Tipine bağlı olarak array, kullanılan gerekmez etti sizeof(array) / sizeof(array[0])halinde arraybir dizi ya olduğu char, unsigned charya da signed char- C18,6.5.3.4 / 4'ten Alıntı: "sizeof tipi kömürü, unsigned char veya imzalı karakter vardır bir işlenen uygulandığında , (veya nitelikli bir sürümü) sonucu 1 olur. " Bu durumda, sizeof(array)özel cevabımda açıklandığı gibi yapabilirsiniz .
RobertS, Monica Cellio

15

C dizisinin boyutu:

int a[10];
size_t size_of_array = sizeof(a);      // Size of array a
int n = sizeof (a) / sizeof (a[0]);    // Number of elements in array a
size_t size_of_element = sizeof(a[0]); // Size of each element in array a                                          
                                       // Size of each element = size of type

4
Meraklı kodu kullanılan size_t size_of_elementhenüz intsahip int n = sizeof (a) / sizeof (a[0]); değilsize_t n = sizeof (a) / sizeof (a[0]);
chux - Eski Monica

1
Merhaba @ Yogeesh HT, chux şüphesine cevap verebilir misiniz. Ben de nasıl int n = sizeof (a) / sizeof (a [0]) dizi uzunluğu veriyor ve neden dizi uzunluğu için size_t kullanmıyor bilmek merak ediyorum. Birisi cevaplayabilir mi?
Beyin

1
@Brain sizeof (a), dizide bulunan tüm elemanların sizeof değerini bir sizeof (a [0]), 1. elemanların boyutlarını verir. Diyelim ki a = {1,2,3,4,5}; sizeof (a) = 20 bayt (eğer sizeof (int) = 4 bayt 5 ile çarpılırsa), sizeof (a [0]) = 4 bayt, yani 20/4 = 5 yani eleman yok
Yogeesh HT

2
@YogeeshHT için çok büyük diziler gibi char a[INT_MAX + 1u];, int nkullanılan int n = sizeof (a) / sizeof (a[0]);(UB olan) yetersizdir. Kullanmak size_t n = sizeof (a) / sizeof (a[0]);bu sorunla karşılaşmaz.
chux - Monica'yı

13

Ben sizeofburada göstermek son iki durumda olan elemanların sayısı veya bayt, herhangi bir dizi iki farklı boyutlardan birini almak için (kullanılabilir olsa bile) asla kullanmanızı tavsiye ederim. İki boyutun her biri için, aşağıda gösterilen makrolar daha güvenli hale getirmek için kullanılabilir. Bunun nedeni açıktır maintainers'ı kod niyeti ve fark yaratmak için sizeof(ptr)gelen sizeof(arr)ilk bakışta böcek sonra herkes kodunu okumak için ortada böylece, (bu şekilde yazılı açık değildir).


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)[0]) * ARRAY_SIZE(arr))

must_be_array(arr)(aşağıda tanımlanmıştır) -Wsizeof-pointer-divBuggy olduğu gibi IS gerekli (nisan / 2020 itibariyle):

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

Bu konuyla ilgili önemli hatalar oluştu: https://lkml.org/lkml/2015/9/3/428

Ben işlev parametreleri için dizi gösterimi asla kullanmak Linus sağladığı çözüm ile katılmıyorum.

Dizi gösterimi bir işaretçi dizi olarak kullanılan belgeler olarak seviyorum. Ancak bu, hata kodunun yazılmasının imkansız olması için kusursuz bir çözümün uygulanması gerektiği anlamına gelir.

Bir diziden bilmek isteyebileceğimiz üç boyut var:

  • Dizi öğelerinin boyutu
  • Dizideki öğelerin sayısı
  • Dizinin bellekte kullandığı bayt cinsinden boyut

Dizi öğelerinin boyutu

Birincisi çok basit ve bir dizi veya işaretçi ile uğraşmamızın önemi yok, çünkü aynı şekilde yapıldı.

Kullanım örneği:

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

qsort() üçüncü argüman olarak bu değere ihtiyaç duyar.


Sorunun konusu olan diğer iki boyut için, bir dizi ile uğraştığımızdan emin olmak ve eğer değilse derlemeyi kırmak istiyoruz, çünkü bir işaretçi ile uğraşırsak yanlış değerler alırız . Derleme bozulduğunda, bir dizi ile uğraşmadığımızı, ancak bunun yerine bir işaretçi ile kolayca görebileceğiz ve kodu, değişkenin boyutunu depolayan bir değişken veya makro ile yazmamız gerekecek. İşaretçinin arkasındaki dizi.


Dizideki öğelerin sayısı

Bu en yaygın olanıdır ve birçok yanıt size tipik ARRAY_SIZE makrosunu sağlamıştır:

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

ARRAY_SIZE sonucunun yaygın olarak işaretli tür değişkenleriyle kullanıldığı göz önüne alındığında ptrdiff_t, bu makronun işaretli bir varyantını tanımlamak iyidir:

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

Birden fazla PTRDIFF_MAXüyesi olan diziler , makronun bu imzalı sürümü için geçersiz değerler verecek, ancak C17 :: 6.5.6.9 okunurken, bunun gibi diziler zaten ateşle oynuyor. Sadece ARRAY_SIZEve size_tbu durumlarda kullanılmalıdır.

GCC 8 gibi derleyicilerin son sürümleri, bu makroyu bir işaretçiye uyguladığınızda sizi uyaracaktır, bu nedenle güvenlidir (eski derleyicilerle güvenli hale getirmek için başka yöntemler vardır).

Tüm dizinin baytlarındaki boyutu her öğenin boyutuna bölerek çalışır.

Kullanım örnekleri:

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

Bu işlevler dizileri kullanmadıysa, ancak bunları parametre olarak aldıysa, eski kod derlenmezdi, bu nedenle bir hataya sahip olmak imkansız olurdu (son derleyici sürümünün kullanılması veya başka bir hile kullanılması durumunda) ve makro çağrısını aşağıdaki değerle değiştirmemiz gerekir:

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

Dizinin bellekte kullandığı bayt cinsinden boyut

ARRAY_SIZE bir önceki durum için bir çözüm olarak yaygın olarak kullanılır, ancak bu durum nadiren güvenli bir şekilde yazılır, belki daha az yaygın olduğu için.

Bu değeri elde etmenin yaygın yolu kullanmaktır sizeof(arr). Sorun: bir öncekiyle aynı; dizi yerine bir işaretçiniz varsa, programınız çıldırır.

Sorunun çözümü, güvenli olduğunu bildiğimiz daha önceki ile aynı makronun kullanılmasını içerir (bir işaretçiye uygulanırsa derlemeyi keser):

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

Nasıl çalıştığı çok basit: Yaptığı bölümü geri alır ARRAY_SIZE, bu nedenle matematiksel iptallerden sonra sadece bir tane ile sonuçlanırsınız sizeof(arr), ancak yapının ek güvenliği ile ARRAY_SIZE.

Kullanım örneği:

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

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

memset() üçüncü argüman olarak bu değere ihtiyaç duyar.

Daha önce olduğu gibi, dizi parametre (işaretçi) olarak alınırsa, derlenmez ve makro çağrısını aşağıdaki değerle değiştirmeniz gerekir:

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

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

Güncelleme (23 / nisan / 2020): -Wsizeof-pointer-divbuggy :

Bugün, GCC'deki yeni uyarının yalnızca makro bir sistem başlığı olmayan bir başlıkta tanımlanmışsa işe yaradığını öğrendim. Makroyu sisteminizde yüklü olan bir başlıkta (genellikle /usr/local/include/veya /usr/include/) ( #include <foo.h>) tanımlarsanız, derleyici bir uyarı yaymaz (GCC 9.3.0'ı denedim).

Bu yüzden #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))güvenli hale getirmek istiyoruz ve istiyoruz. C11 _Static_assert()ve bazı GCC uzantılarına ihtiyacımız olacak : İfadelerdeki İfadeler ve Beyanlar , __builtin_types_compatible_p :

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

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

Şimdi ARRAY_SIZE()tamamen güvenlidir ve bu nedenle tüm türevleri güvende olacaktır.


Güncelleme: libbsd şunları sağlar __arraycount():

Libbsd makro sağlar __arraycount()içinde <sys/cdefs.h>parantez çifti bulunmadığı için güvensiz, ama biz bu parantezleri kendimizi ekleyebilir ve bu nedenle biz bile (neden biz zaten var o kodu çoğaltmak istiyorum bizim başlığındaki bölünme yazmaya gerek yok mu? ). Bu makro bir sistem başlığında tanımlanır, bu yüzden onu kullanırsak yukarıdaki makroları kullanmak zorunda kalırız.

#include <sys/cdefs.h>


#define is_same_type(a, b)      __builtin_types_compatible_p(typeof(a), typeof(b))
#define is_array(a)             (!is_same_type((a), &(a)[0]))
#define Static_assert_array(a)  _Static_assert(is_array(a), "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))

Bazı sistemler sağlamak nitems()içinde <sys/param.h>yerine ve bazı sistemler hem sağlarlar. Sisteminizi kontrol etmeli ve sahip olduğunuz sistemi kullanmalısınız ve her ikisi de taşınabilirlik ve destek için bazı önişlemci koşullarını kullanmalısınız.


Güncelleme: Makronun dosya kapsamında kullanılmasına izin verin:

Maalesef, ({})gcc uzantısı dosya kapsamında kullanılamaz. Makroyu dosya kapsamında kullanabilmek için, statik bildirimin içinde olması gerekir sizeof(struct {}). Ardından, 0sonucu etkilememek için ile çarpın . Oyuncular (int), geri dönen bir işlevi simüle etmek için iyi olabilir (int)0(bu durumda gerekli değildir, ancak daha sonra başka şeyler için tekrar kullanılabilir).

#include <sys/cdefs.h>


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

#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)[0]) * ARRAY_SIZE(arr))

3
Neden düşüşü açıklamak ister misiniz? Bu Şekil bir güvenli ve ortak yapı (bir çözüm sizeof(arr)başka gösterilmemiştir): ARRAY_BYTES(arr).
Cacahuete Frito

2
ARRAY_SIZE serbestçe kullanılacak kadar yaygındır ve ARRAY_BYTES adında çok açıktır, ARRAY_SIZE'nin yanında tanımlanmalıdır, böylece bir kullanıcı her ikisini de kolayca görebilir ve kullanımı sayesinde, kodu okuyan herkesin ne hakkında şüpheleri olduğunu sanmıyorum öyle. Demek istediğim basit kullanmak değil sizeof, bunun yerine bu yapıları kullanmak; Bu yapıları her seferinde yazmak istiyorsanız, muhtemelen bir hata yapacaksınız (yapıştırmayı kopyalarsanız çok yaygındır ve her seferinde çok fazla parantezleri olduğu için yazarsanız çok yaygındır) ...
Cacahuete Frito

3
..., bu yüzden ana sonuç üzerinde duruyorum: tek sizeofbir açıkça güvensiz (nedenler cevapta) ve makroları kullanmıyorum ama sağladığım yapıları kullanmak her seferinde daha güvensiz, bu yüzden gitmenin tek yolu makrolardır.
Cacahuete Frito

3
@MarkHarrison İşaretçiler ve diziler arasındaki farkı biliyorum. Ama daha sonra küçük fonksiyonlara yeniden baktığım bir fonksiyonum vardı ve ilk önce bir dizi olan, daha sonra bir işaretçi oldu ve bu, eğer sizeof'i değiştirmeyi unutursanız, vidaladığınız ve görmemenizin kolay olduğu bir nokta onlardan biri.
Cacahuete Frito

3
@hyde Ayrıca farkın herkesin farkı bildiği anlamına gelmediğini ve neden bu hataların% 100'ünü ortadan kaldıran bir şey kullanmıyorum? Bu hata neredeyse Linux'a dönüştü; Linus'a geldi, bu da çok fazla incelemeden geçtiğini ve aynı hatanın dediği gibi çekirdeğin başka bir bölümünde Linux'a girme olasılığı olduğu anlamına geliyor.
Cacahuete Frito

11

"kendini ayağından vurmanın ince bir yolunu tanıttın"

C 'doğal' diziler boyutlarını saklamaz. Bu nedenle, dizinin uzunluğunu ayrı bir değişken / const içine kaydetmeniz ve diziyi her geçirdiğinizde geçirmeniz önerilir:

#define MY_ARRAY_LENGTH   15
int myArray[MY_ARRAY_LENGTH];

Her zaman yerel dizilerden kaçınmalısınız (bu durumda ayağınıza dikkat edemezseniz). C ++ yazıyorsanız, STL'nin 'vektör' kapsayıcısını kullanın. "Dizilerle karşılaştırıldığında neredeyse aynı performansı sağlıyorlar" ve çok daha kullanışlılar!

// vector is a template, the <int> means it is a vector of ints
vector<int> numbers;  

// push_back() puts a new value at the end (or back) of the vector
for (int i = 0; i < 10; i++)
    numbers.push_back(i);

// Determine the size of the array
cout << numbers.size();

Bkz. Http://www.cplusplus.com/reference/stl/vector/


C'de tamsayı sabitlerini bildirmenin doğru yolunun bir enumbildirim kullanmak olduğunu okudum .
Raffi Khatchadourian

Soru C ++ ile değil C ile ilgilidir. Yani STL yok.
Cacahuete Frito

11
#define SIZE_OF_ARRAY(_array) (sizeof(_array) / sizeof(_array[0]))

6
Bunun yalnızca diziler için çalıştığını, dizilere işaret eden işaretçiler için geçerli olmadığını unutmayın.
David Schwartz

5

Gerçekten dizi etrafında geçmek için yapmak istiyorsanız ben bir dizi istediğiniz tipe bir işaretçi ve dizinin boyutunu temsil eden bir tamsayı depolamak için bir yapı kullanmanızı öneririz. Sonra bunu işlevlerinize aktarabilirsiniz. Bu değişkene dizi değişken değerini (ilk öğeye işaretçi) atamanız yeterlidir. Daha sonra Array.arr[i]i-th öğesini almaya gidebilir Array.sizeve dizideki öğe sayısını almak için kullanabilirsiniz .

Size bazı kodlar ekledim. Çok kullanışlı değil, ancak daha fazla özellik ile genişletebilirsiniz. Dürüst olmak gerekirse, bunlar istediğiniz şeylerse, C'yi kullanmayı bırakmalı ve yerleşik bu özelliklerle başka bir dil kullanmalısınız.

/* Absolutely no one should use this...
   By the time you're done implementing it you'll wish you just passed around
   an array and size to your functions */
/* This is a static implementation. You can get a dynamic implementation and 
   cut out the array in main by using the stdlib memory allocation methods,
   but it will work much slower since it will store your array on the heap */

#include <stdio.h>
#include <string.h>
/*
#include "MyTypeArray.h"
*/
/* MyTypeArray.h 
#ifndef MYTYPE_ARRAY
#define MYTYPE_ARRAY
*/
typedef struct MyType
{
   int age;
   char name[20];
} MyType;
typedef struct MyTypeArray
{
   int size;
   MyType *arr;
} MyTypeArray;

MyType new_MyType(int age, char *name);
MyTypeArray newMyTypeArray(int size, MyType *first);
/*
#endif
End MyTypeArray.h */

/* MyTypeArray.c */
MyType new_MyType(int age, char *name)
{
   MyType d;
   d.age = age;
   strcpy(d.name, name);
   return d;
}

MyTypeArray new_MyTypeArray(int size, MyType *first)
{
   MyTypeArray d;
   d.size = size;
   d.arr = first;
   return d;
}
/* End MyTypeArray.c */


void print_MyType_names(MyTypeArray d)
{
   int i;
   for (i = 0; i < d.size; i++)
   {
      printf("Name: %s, Age: %d\n", d.arr[i].name, d.arr[i].age);
   }
}

int main()
{
   /* First create an array on the stack to store our elements in.
      Note we could create an empty array with a size instead and
      set the elements later. */
   MyType arr[] = {new_MyType(10, "Sam"), new_MyType(3, "Baxter")};
   /* Now create a "MyTypeArray" which will use the array we just
      created internally. Really it will just store the value of the pointer
      "arr". Here we are manually setting the size. You can use the sizeof
      trick here instead if you're sure it will work with your compiler. */
   MyTypeArray array = new_MyTypeArray(2, arr);
   /* MyTypeArray array = new_MyTypeArray(sizeof(arr)/sizeof(arr[0]), arr); */
   print_MyType_names(array);
   return 0;
}

1
Taşma işlemi yapılmayan kod kaldırılamıyor strcpy(d.name, name);.
chux - Monica'yı

4

En iyi yol, bu bilgileri örneğin bir yapıya kaydetmektir:

typedef struct {
     int *array;
     int elements;
} list_s;

Oluşturma, yok etme, eşitliği kontrol etme ve ihtiyacınız olan diğer her şey gibi gerekli tüm işlevleri uygulayın. Parametre olarak geçmek daha kolaydır.


6
İçin bir nedeni var int elementsvs. size_t elements?
chux - Monica'yı eski durumuna getir

3

İşlev sizeofdiziniz tarafından bellekte kullanılan bayt sayısını döndürür. Dizinizdeki öğe sayısını hesaplamak istiyorsanız, bu sayıyı dizinin sizeofdeğişken türüne bölmelisiniz . Diyelim ki int array[10];, bilgisayarınızdaki değişken türü tamsayı 32 bit (veya 4 bayt) ise, dizinizin boyutunu elde etmek için aşağıdakileri yapmanız gerekir:

int array[10];
int sizeOfArray = sizeof(array)/sizeof(int);

2

&Operatörü kullanabilirsiniz . Kaynak kod:

#include<stdio.h>
#include<stdlib.h>
int main(){

    int a[10];

    int *p; 

    printf("%p\n", (void *)a); 
    printf("%p\n", (void *)(&a+1));
    printf("---- diff----\n");
    printf("%zu\n", sizeof(a[0]));
    printf("The size of array a is %zu\n", ((char *)(&a+1)-(char *)a)/(sizeof(a[0])));


    return 0;
};

İşte örnek çıktı

1549216672
1549216712
---- diff----
4
The size of array a is 10

7
Ben aşağı itmedim, ama bu bir tuğla ile çivi vurmak gibi çünkü senin yanında yatan bir çekiç fark etmedi. Ayrıca, insanlar başlatılmamış değişkenleri kullanarak kaşlarını çatmaya eğilimlidir ... ama sanırım burada amacınıza yeterince iyi hizmet ediyor.
Dmitri

2
@Dmitri Burada başlatılmamış değişkenlere erişilmedi
MM

1
Hmmm. İşaretçinin çıkarılması yol açar ptrdiff_t. sizeof()ile sonuçlanır size_t. C hangisinin daha geniş veya daha yüksek / aynı rütbe olduğunu tanımlamaz. Dolayısıyla bölümün türü ((char *)(&a+1)-(char *)a)/(sizeof(a[0]))kesinlikle değildir size_tve bu nedenle ile baskı zyapmak UB'ye yol açabilir. Basitçe kullanmak printf("The size of array a is %zu\n", sizeof a/sizeof a[0]);yeterlidir.
chux - Monica'yı

1
(char *)(&a+1)-(char *)asabit değildir ve sabit boyutta bile çalışma zamanında hesaplanabilir a[10]. sizeof(a)/sizeof(a[0])bu durumda derleme zamanında sabit yapılır.
chux - Monica'yı

1

En basit cevap:

#include <stdio.h>

int main(void) {

    int a[] = {2,3,4,5,4,5,6,78,9,91,435,4,5,76,7,34};//for Example only
    int size;

    size = sizeof(a)/sizeof(a[0]);//Method

    printf ("size = %d",size);
    return 0;
}


0

Halihazırda verilen cevapların yanı sıra,

sizeof(a) / sizeof (a[0])

Eğer abirinin bir dizidir char, unsigned charya signed charkullanmak gerekmez sizeofbir yana iki kez sizeofher zaman için neden yok bu tür bir işlenen ifade 1.

C18,6.5.3.4 / 4'ten alıntı:

" Ne zaman sizeofbir tür olan işlenen uygulanır char, unsigned charya da signed char, (ya da bunun bir yetkili versiyonu) sonucudur 1."

Bu durumda, sizeof(a) / sizeof (a[0])eşdeğer olacaktır NUMBER OF ARRAY ELEMENTS / 1eğer atipte bir dizi char, unsigned charveya signed char. 1'e bölünme gereksizdir.

Bu durumda, basitçe kısaltabilir ve yapabilirsiniz:

sizeof(a)

Örneğin:

char a[10];
size_t length = sizeof(a);

Bir kanıt istiyorsanız, işte GodBolt'a bir bağlantı .


Bununla birlikte, tür önemli ölçüde değişirse (bu durumlar nadir olmasına rağmen) bölüm güvenliği korur.


1
Muhtemelen bölümle birlikte bir makro uygulamayı tercih edersiniz, çünkü tür gelecekte değişebilir (belki de olası olmasa da) ve bölüm derleme zamanında bilinir, bu nedenle derleyici bunu optimize eder (lütfen değiştirmezse) derleyiciniz).
Cacahuete Frito

1
@CacahueteFrito Evet, bu arada ben de düşündüm. Cevabı yan not olarak aldım. Teşekkür ederim.
RobertS, Monica Cellio'yu

-1

Not: Bu, yorumda MM'nin işaret ettiği gibi tanımlanmamış davranışlar verebilir.

int a[10];
int size = (*(&a+1)-a) ;

Daha fazla ayrıntı için buraya ve buraya bakınız .


2
Bu teknik olarak tanımlanmamış bir davranıştır; *Operatör bir son-uç işaretçi uygulanmayabilir
AA

3
"tanımlanmamış davranış", C Standardının davranışı tanımlamadığı anlamına gelir. Programınızda denerseniz o zaman her şey olabilir
MM

@MM diyorsun *(&a+1) - a;farklıdır (&a)[1] - a;hem yukarıda yok *(&a+1)ve (&a)[1]sonunda geçmiş 1 sayılır?
QuentinUK

@QuentinUK iki ifadeniz aynı,x[y]*(x + (y))
MM

@MM diye düşündüm. Ancak Arjun Sreedharan'ın diğer cevabı 38 yukarı ok ve bunun -1'i var. Arjun Sreedharan'ın cevabında tanımlanmamış davranıştan bahsedilmiyor.
QuentinUK
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.