Jak określić rozmiar mojej tablicy w C?


Odpowiedzi:


1259

Streszczenie:

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

Pełna odpowiedź:

Aby określić rozmiar tablicy w bajtach, możesz użyć sizeof operatora:

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

Na moim komputerze ints mają 4 bajty, więc n ma 68.

Aby określić liczbę elementów w tablicy, możemy podzielić całkowity rozmiar tablicy przez rozmiar elementu tablicy. Możesz to zrobić z tym typem:

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

i uzyskać prawidłową odpowiedź (68/4 = 17), ale jeśli azmienił się typ , miałbyś paskudny błąd, gdybyś również zapomniał zmienić sizeof(int).

Tak więc preferowanym dzielnikiem jest sizeof(a[0])lub równoważny sizeof(*a), rozmiar pierwszego elementu tablicy.

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

Kolejną zaletą jest to, że możesz teraz łatwo sparametryzować nazwę tablicy w makrze i uzyskać:

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

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

6
Wygenerowany kod będzie identyczny, ponieważ kompilator zna typ * int_arr w czasie kompilacji (a zatem wartość sizeof (* int_arr)). Będzie stała, a kompilator może odpowiednio zoptymalizować.
Mark Harrison

10
Tak powinno być w przypadku wszystkich kompilatorów, ponieważ wyniki sizeof są zdefiniowane jako stała czasowa kompilacji.
Mark Harrison

449
Ważne : nie przestawaj czytać tutaj, przeczytaj następną odpowiedź! Działa to tylko dla tablic na stosie , np. Jeśli używasz malloc () lub uzyskujesz dostęp do parametru funkcji, nie masz szczęścia. Patrz poniżej.
Markus

7
Do programowania interfejsu API systemu Windows w C lub C ++ istnieje ARRAYSIZEmakro zdefiniowane w WinNT.h(które jest pobierane przez inne nagłówki). Dlatego użytkownicy WinAPI nie muszą definiować własnego makra.
Lumi,

17
@ Markus działa dla każdej zmiennej, która ma typ tablicy; to nie musi być „na stosie”. Np static int a[20];. Ale twój komentarz jest przydatny dla czytelników, którzy mogą nie zdawać sobie sprawy z różnicy między tablicą a wskaźnikiem.
MM

807

sizeofDroga jest właściwa droga IFF masz do czynienia z tablicami nie odebranych jako parametry. Tablica wysłana jako parametr do funkcji jest traktowana jako wskaźnik, więc sizeofzwróci rozmiar wskaźnika, zamiast rozmiaru tablicy.

Dlatego w funkcjach wewnętrznych ta metoda nie działa. Zamiast tego zawsze przekazuj dodatkowy parametr size_t sizewskazujący liczbę elementów w tablicy.

Test:

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

Dane wyjściowe (w 64-bitowym systemie operacyjnym Linux):

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

Dane wyjściowe (w 32-bitowym systemie operacyjnym Windows):

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

10
dlaczego jest tak, length of parameter:2jeśli przekazywany jest tylko wskaźnik do pierwszego elementu tablicy?
Bbvarghe,

16
@Bbvarghe To dlatego, że wskaźniki w systemach 64-bitowych mają 8 bajtów (sizeof (intArray)), ale ints wciąż (zwykle) mają 4 bajty (sizeof (intArray [0])).
Elideb

13
@Pacerier: Nie ma poprawnego kodu - zwykle rozwiązaniem jest podanie długości wraz z tablicą jako osobnego argumentu.
Jean Hominal

10
Zaczekaj, więc nie ma sposobu, aby uzyskać dostęp do tablicy bezpośrednio ze wskaźnika i zobaczyć jej rozmiar? Nowy w C tutaj.
sudo

7
@Michael Trouw: można użyć składni operatora, jeśli to sprawia, że czujesz lepiej: (sizeof array / sizeof *array).
chqrlie

134

Warto zauważyć, że sizeofnie pomaga to w przypadku wartości tablicy, która uległa rozkładowi na wskaźnik: mimo że wskazuje na początek tablicy, w przypadku kompilatora jest ona taka sama jak wskaźnik do pojedynczego elementu tej tablicy . Wskaźnik nie „pamięta” niczego innego o tablicy, która została użyta do jego zainicjowania.

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: Standard definiuje rozmiarof jako uzyskiwanie liczby bajtów w obiekcie, a rozmiarof (char) jest zawsze jeden. Liczba bitów w bajcie zależy od implementacji. Edycja: ANSI C ++ sekcja standardowa 5.3.3 Sizeof: "Operator sizeof zwraca liczbę bajtów w reprezentacji obiektowej swojego argumentu. [...] sizeof (char), sizeof (char podpisany) i sizeof (char podpisany) to 1; wynik sizeof zastosowany do dowolnego innego podstawowego typu jest zdefiniowany w implementacji. ”
Skizz

Sekcja 1.6 Model pamięci C ++: „Podstawową jednostką pamięci w modelu pamięci C ++ jest bajt. Bajt jest wystarczająco duży, aby pomieścić dowolny element podstawowego zestawu znaków wykonawczych i składa się z ciągłej sekwencji bitów, liczby z których jest zdefiniowany implementacja. ”
Skizz

2
Pamiętam, że CRAY miał C z char32 bitami. Wszystko, co mówi standard, to to, że mogą być reprezentowane wartości całkowite od 0 do 127, a jego zakres wynosi co najmniej -127 do 127 (znak jest podpisany) lub 0 do 255 (znak jest niepodpisany).
vonbrand

5
To doskonała odpowiedź. Chcę skomentować, że wszystkie powyższe twierdzenia są ocenione na PRAWDA.
Javad

49

Rozmiar „sztuczki” to najlepszy sposób, jaki znam, z jedną małą, ale (dla mnie, to jest główną wkurzaniem) istotną zmianą w stosowaniu nawiasów.

Jak wyjaśnia wpis w Wikipedii, C sizeofnie jest funkcją; to operator . Dlatego nie wymaga nawiasów wokół argumentu, chyba że argument jest nazwą typu. Łatwo to zapamiętać, ponieważ sprawia, że ​​argument wygląda jak rzutowane wyrażenie, które również używa nawiasów.

Więc: jeśli masz następujące elementy:

int myArray[10];

Możesz znaleźć liczbę elementów o takim kodzie:

size_t n = sizeof myArray / sizeof *myArray;

To dla mnie brzmi o wiele łatwiej niż alternatywa z nawiasami. Popieram także użycie gwiazdki w prawej części działu, ponieważ jest bardziej zwięzła niż indeksowanie.

Oczywiście, to także czas kompilacji, więc nie musisz się martwić podziałem wpływającym na wydajność programu. Więc skorzystaj z tego formularza, gdziekolwiek możesz.

Zawsze najlepiej jest używać sizeof na rzeczywistym obiekcie, gdy go masz, a nie na typie, ponieważ wtedy nie musisz się martwić o popełnienie błędu i podanie niewłaściwego typu.

Załóżmy na przykład, że masz funkcję, która wyprowadza niektóre dane jako strumień bajtów, na przykład przez sieć. Wywołajmy funkcję send()i sprawmy, aby jako argumenty przyjmowała wskaźnik do obiektu do wysłania oraz liczbę bajtów w obiekcie. Prototyp staje się:

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

A potem musisz wysłać liczbę całkowitą, więc kodujesz to w ten sposób:

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

Teraz wprowadziłeś subtelny sposób strzelania sobie w stopę, określając typ foow dwóch miejscach. Jeśli jeden się zmieni, a drugi nie, kod się zepsuje. Dlatego zawsze rób tak:

send(&foo, sizeof foo);

Teraz jesteś chroniony. Jasne, powielasz nazwę zmiennej, ale ma duże prawdopodobieństwo uszkodzenia w sposób, który kompilator może wykryć, jeśli ją zmienisz.


Przy okazji, czy są to identyczne instrukcje na poziomie procesora? Czy sizeof(int)wymaga mniej instrukcji niż sizeof(foo)?
Pacerier

@Pacerier: nie, są identyczne. Pomyśl o int x = 1+1;kontra int x = (1+1);. Tutaj nawiasy są absolutnie wyłącznie estetyczne.
quetzalcoatl

@Aidiakapi To nieprawda, weź pod uwagę C99 VLA.
zrelaksuj się

@ unwind Dzięki, poprawiam się. Aby poprawić mój komentarz, sizeofzawsze będzie stały w C ++ i C89. W przypadku tablic o zmiennej długości C99 można go oceniać w czasie wykonywania.
Aidiakapi

2
sizeofmoże być operatorem, ale zgodnie z Linusem Torvaldsem należy go traktować jako funkcję. Zgadzam się. Przeczytaj jego uzasadnienie tutaj: lkml.org/lkml/2012/7/11/103
Dr Osoba Osoba II

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

Sprawdź ten link do wyjaśnienia


7
Mały nitpick: wynik odejmowania wskaźnika ma typ ptrdiff_t. (Zwykle w systemie 64-bitowym będzie to większy typ int). Nawet jeśli zmienisz intna ptrdiff_tw tym kodzie, nadal zawiera błąd, jeśli arrzajmuje więcej niż połowę przestrzeni adresowej.
MM

2
@MM Kolejny mały nitpick: W zależności od architektury systemu przestrzeń adresowa nie jest tak duża, jak rozmiar wskaźnika w większości systemów. Na przykład Windows ogranicza przestrzeń adresową dla aplikacji 64-bitowych do 8 TB lub 44 bitów. Więc nawet jeśli masz tablicę większą niż połowa przestrzeni adresowej 4.1 TB, na przykład, nie będzie to błąd. Tylko jeśli Twoja przestrzeń adresowa przekracza 63 bity w tych systemach, można nawet napotkać taki błąd. Ogólnie nie martw się o to.
Aidiakapi

1
@Aidiakapi w 32-bitowym systemie Linux x86 lub Windows z /3Gopcją masz podział użytkownika / jądra 3G / 1G, co pozwala mieć rozmiar tablic do 75% wielkości przestrzeni adresowej.
Ruslan

1
Uważaj foo buf1[80]; foo buf2[sizeof buf1/sizeof buf1[0]]; foo buf3[(&buf1)[1] - buf1];za zmienne globalne. buf3[]deklaracja kończy się niepowodzeniem, ponieważ (&buf1)[1] - buf1nie jest stała.
chux - Przywróć Monikę

2
Jest to technicznie niezdefiniowane zachowanie, ponieważ standard wyraźnie nie zezwala na dereferencje poza końcem tablicy (nawet jeśli nie spróbujesz odczytać zapisanej wartości)
MM


21

Jeśli znasz typ danych tablicy, możesz użyć czegoś takiego:

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

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

Lub jeśli nie znasz typu danych tablicy, możesz użyć czegoś takiego:

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

Uwaga: Ta funkcja działa tylko wtedy, gdy tablica nie jest zdefiniowana w czasie wykonywania (np. Malloc), a tablica nie jest przekazywana w funkcji. W obu przypadkach arr(nazwa tablicy) jest wskaźnikiem.


4
int noofele = sizeof(arr)/sizeof(int);jest tylko w połowie lepszy niż kodowanie int noofele = 9;. Użycie sizeof(arr)zachowuje elastyczność w przypadku zmiany rozmiaru tablicy. Jednak sizeof(int)wymaga aktualizacji, jeśli rodzaj arr[]zmiany. Lepszy w użyciu, sizeof(arr)/sizeof(arr[0])nawet jeśli typ jest dobrze znany. Niejasne, dlaczego intdla for noofelevs. size_t, typ zwracany przez sizeof().
chux - Przywróć Monikę

19

Makro, z ARRAYELEMENTCOUNT(x)którego wszyscy korzystają, ocenia niepoprawnie . W rzeczywistości jest to tylko kwestia drażliwa, ponieważ nie można mieć wyrażeń, które skutkowałyby typem „tablicy”.

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

ARRAYELEMENTCOUNT(p + 1);

Faktycznie ocenia się jako:

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

Natomiast

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

ARRAYELEMENTCOUNT(p + 1);

Poprawnie ocenia:

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

To naprawdę nie ma wiele wspólnego z rozmiarem tablic. Właśnie zauważyłem wiele błędów wynikających z nieobserwowania, jak działa preprocesor C. Zawsze zawijasz parametr makra, w którym nie może być zaangażowane żadne wyrażenie.


To jest poprawne; mój przykład był zły. Ale tak właśnie powinno być. Jak już wspomniałem p + 1, skończy jako typ wskaźnika i unieważni całe makro (podobnie jak w przypadku próby użycia makra w funkcji z parametrem wskaźnika).

Na koniec dnia, w tym konkretnym przypadku, błąd tak naprawdę nie ma znaczenia (więc po prostu marnuję czas wszystkich; huzzah!), Ponieważ nie masz wyrażeń z rodzajem „tablicy”. Ale tak naprawdę kwestia subtelności oceny preprocesora uważam za ważną.


2
Dziękuję za wyjaśnienie. Oryginalna wersja powoduje błąd czasu kompilacji. Clang informuje, że „wartość w indeksie nie jest tablicą, wskaźnikiem ani wektorem”. W tym przypadku wydaje się to preferowane zachowanie, chociaż twoje komentarze na temat kolejności oceny w makrach są dobrze przyjęte.
Mark Harrison

1
Nie myślałem o reklamacji kompilatora jako automatycznym powiadomieniu niepoprawnego typu. Dziękuję Ci!

3
Czy istnieje powód, aby nie używać (sizeof (x) / sizeof (*x))?
poważnedev

16

W przypadku tablic wielowymiarowych jest to nieco bardziej skomplikowane. Często ludzie definiują wyraźne stałe makr, tj

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

Ale te stałe można również ocenić w czasie kompilacji za pomocą 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);

Zauważ, że ten kod działa w C i C ++. Do tablic o więcej niż dwóch wymiarach użyj

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

itp., ad infinitum.


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

Zależnie od typu arrayma, nie musisz używać, sizeof(array) / sizeof(array[0])jeśli arrayjest tablicą albo char, unsigned charalbo signed char- Cytat z C18,6.5.3.4 / 4: „Gdy sizeof jest stosowane do operandu, który ma typ char, unsigned char lub podpisany char , (lub jego kwalifikowana wersja) wynik to 1. ” W takim przypadku możesz po prostu zrobić, sizeof(array)jak wyjaśniono w mojej dedykowanej odpowiedzi .
RobertS obsługuje Monikę Cellio

15

Rozmiar tablicy w C:

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
Ciekawy kod, który używany size_t size_of_elementjeszcze intz int n = sizeof (a) / sizeof (a[0]); niesize_t n = sizeof (a) / sizeof (a[0]);
chux - przywrócenie Monica

1
Cześć @Yogeesh HT, czy możesz odpowiedzieć na wątpliwości dotyczące chuxa? Jestem również bardzo ciekawy, jak int n = sizeof (a) / sizeof (a [0]) podaje długość tablicy i dlaczego nie używamy size_t dla długości tablicy. Czy ktoś może na to odpowiedzieć?
Brain

1
@Brain sizeof (a) daje rozmiar wszystkich elementów obecnych w tablicy a sizeof (a [0]) daje rozmiar 1 elementów. Załóżmy, że a = {1,2,3,4,5}; sizeof (a) = 20 bajtów (jeśli sizeof (int) = 4 bajty mnożą 5), sizeof (a [0]) = 4 bajty, więc 20/4 = 5 tzn. brak elementów
Yogeesh HT

2
@YogeeshHT W przypadku bardzo dużych tablic char a[INT_MAX + 1u];, int njak użyte w int n = sizeof (a) / sizeof (a[0]);jest niewystarczające (jest to UB). Korzystanie size_t n = sizeof (a) / sizeof (a[0]);nie powoduje tego problemu.
chux - Przywróć Monikę

13

Radziłbym nigdy nie używać sizeof(nawet jeśli można go użyć), aby uzyskać dowolny z dwóch różnych rozmiarów tablicy, albo w liczbie elementów, albo w bajtach, które są dwoma ostatnimi przypadkami, które tutaj pokazuję. Dla każdego z dwóch rozmiarów można użyć makr pokazanych poniżej, aby zwiększyć bezpieczeństwo. Powodem jest ujawnienie opiekunom intencji kodu i odróżnienie go sizeof(ptr)od sizeof(arr)pierwszego spojrzenia (które napisane w ten sposób nie jest oczywiste), aby błędy były widoczne dla wszystkich czytających kod.


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)(zdefiniowane poniżej) JEST potrzebne, ponieważ -Wsizeof-pointer-divjest wadliwe (na kwiecień / 2020 r.):

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

Pojawiły się ważne błędy dotyczące tego tematu: https://lkml.org/lkml/2015/9/3/428

Nie zgadzam się z rozwiązaniem, które zapewnia Linus, którym jest nigdy nie używać notacji tablicowej do parametrów funkcji.

Lubię notację tablicową jako dokumentację, że wskaźnik jest używany jako tablica. Oznacza to jednak, że należy zastosować rozwiązanie niezawodne, aby nie można było napisać błędnego kodu.

Z tablicy mamy trzy rozmiary, które chcielibyśmy poznać:

  • Rozmiar elementów tablicy
  • Liczba elementów w tablicy
  • Rozmiar w bajtach wykorzystywany przez tablicę w pamięci

Rozmiar elementów tablicy

Pierwszy jest bardzo prosty i nie ma znaczenia, czy mamy do czynienia z tablicą czy wskaźnikiem, ponieważ robi się to w ten sam sposób.

Przykład użycia:

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

qsort() potrzebuje tej wartości jako trzeciego argumentu.


W przypadku pozostałych dwóch rozmiarów, które są tematem pytania, chcemy się upewnić, że mamy do czynienia z tablicą, a jeśli nie, zepsujemy kompilację, ponieważ jeśli mamy do czynienia ze wskaźnikiem, otrzymamy nieprawidłowe wartości . Gdy kompilacja zostanie zerwana, będziemy mogli łatwo zobaczyć, że nie mamy do czynienia z tablicą, ale ze wskaźnikiem, i będziemy musieli po prostu napisać kod ze zmienną lub makrem, który przechowuje rozmiar tablica za wskaźnikiem.


Liczba elementów w tablicy

Ten jest najczęstszy i wiele odpowiedzi dostarczyło typowego makra ARRAY_SIZE:

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

Biorąc pod uwagę, że wynik ARRAY_SIZE jest powszechnie używany z podpisanymi zmiennymi typu ptrdiff_t, dobrze jest zdefiniować podpisany wariant tego makra:

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

Tablice z więcej niż PTRDIFF_MAXczłonkami podadzą niepoprawne wartości dla tej podpisanej wersji makra, ale od odczytu C17 :: 6.5.6.9, takie tablice już się bawią ogniem. Tylko ARRAY_SIZEi size_tpowinien być stosowany w tych przypadkach.

Najnowsze wersje kompilatorów, takie jak GCC 8, ostrzeżą cię, gdy zastosujesz to makro do wskaźnika, więc jest to bezpieczne (istnieją inne metody, aby zapewnić bezpieczeństwo w starszych kompilatorach).

Działa, dzieląc rozmiar w bajtach całej tablicy przez rozmiar każdego elementu.

Przykłady użycia:

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

Gdyby te funkcje nie używały tablic, ale zamiast tego otrzymały je jako parametry, poprzedni kod nie skompilowałby się, więc nie byłoby możliwe wystąpienie błędu (biorąc pod uwagę, że użyto najnowszej wersji kompilatora lub zastosowano inną sztuczkę) , i musimy zastąpić wywołanie makra wartością:

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

Rozmiar w bajtach wykorzystywany przez tablicę w pamięci

ARRAY_SIZE jest powszechnie stosowany jako rozwiązanie poprzedniej sprawy, ale ta sprawa rzadko jest pisana bezpiecznie, może dlatego, że jest mniej powszechna.

Typowym sposobem uzyskania tej wartości jest użycie sizeof(arr). Problem: taki sam jak w poprzednim; jeśli zamiast tablicy masz wskaźnik, twój program zwariuje.

Rozwiązanie problemu polega na użyciu tego samego makra, co wcześniej, co wiemy, że jest bezpieczne (przerywa kompilację, jeśli zostanie zastosowane do wskaźnika):

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

Jak to działa, jest bardzo proste: cofa podział, który ARRAY_SIZEdziała, więc po anulowaniu matematyki otrzymujesz tylko jeden sizeof(arr), ale z dodatkowym bezpieczeństwem ARRAY_SIZEkonstrukcji.

Przykład użycia:

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

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

memset() potrzebuje tej wartości jako trzeciego argumentu.

Tak jak poprzednio, jeśli tablica zostanie odebrana jako parametr (wskaźnik), nie zostanie skompilowana i będziemy musieli zastąpić wywołanie makra wartością:

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

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

Aktualizacja (23 kwietnia / 2020): -Wsizeof-pointer-divjest wadliwa :

Dzisiaj dowiedziałem się, że nowe ostrzeżenie w GCC działa tylko wtedy, gdy makro jest zdefiniowane w nagłówku, który nie jest nagłówkiem systemowym. Jeśli zdefiniujesz makro w nagłówku, który jest zainstalowany w twoim systemie (zwykle /usr/local/include/lub /usr/include/) ( #include <foo.h>), kompilator NIE wyśle ​​ostrzeżenia (próbowałem GCC 9.3.0).

Więc mamy #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))i chcemy zapewnić bezpieczeństwo. Będziemy potrzebować C11 _Static_assert()i niektórych rozszerzeń GCC: Instrukcje i deklaracje w wyrażeniach , __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]);                                \
}                                                                       \
)

Teraz ARRAY_SIZE()jest całkowicie bezpieczny, a zatem wszystkie jego pochodne będą bezpieczne.


Aktualizacja: libbsd zapewnia __arraycount():

Libbsd zapewnia makro __arraycount()w <sys/cdefs.h>, co jest niebezpieczne, ponieważ brakuje mu parę nawiasów, ale możemy dodać te nawiasach siebie, a więc nawet nie trzeba pisać podział w naszym nagłówku (dlaczego chcemy powielić kod, który już istnieje? ). To makro jest zdefiniowane w nagłówku systemowym, więc jeśli go użyjemy, będziemy zmuszeni użyć powyższych makr.

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

Niektóre systemy zapewniają nitems()w <sys/param.h>zamian, a niektóre zapewniają oba. Powinieneś sprawdzić swój system i użyć tego, który posiadasz, a być może użyć niektórych warunków wstępnych procesora dla zapewnienia przenośności i obsługi obu.


Aktualizacja: Zezwól na użycie makra w zakresie plików:

Niestety ({})rozszerzenia gcc nie można używać w zakresie plików. Aby móc korzystać z makra w zakresie plików, należy zapewnić statyczne potwierdzenie sizeof(struct {}). Następnie pomnóż go, 0aby nie wpłynąć na wynik. Rzutowanie na (int)może być przydatne do symulacji funkcji, która zwraca (int)0(w tym przypadku nie jest to konieczne, ale można ją wykorzystać do innych celów).

#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
Czy mógłbyś wyjaśnić, dlaczego głosowanie negatywne? Wskazuje to rozwiązanie do niebezpiecznych i wspólnej konstrukcji ( sizeof(arr)), które nie są pokazane: w innym miejscu ARRAY_BYTES(arr).
Cacahuete Frito,

2
ARRAY_SIZE jest na tyle powszechny, że można z niego swobodnie korzystać, a ARRAY_BYTES jest bardzo wyraźny w swojej nazwie, powinien być zdefiniowany obok ARRAY_SIZE, aby użytkownik mógł zobaczyć zarówno łatwo, jak i jego użycie, nie sądzę, aby ktokolwiek czytający kod miał wątpliwości co do tego to robi. Chodziło mi o to, aby nie używać prostego sizeof, ale zamiast tego używać tych konstrukcji; jeśli masz ochotę pisać te konstrukcje za każdym razem, prawdopodobnie popełnisz błąd (bardzo częsty, jeśli kopiujesz wklej, a także bardzo powszechny, jeśli piszesz je za każdym razem, ponieważ mają wiele nawiasów) ...
Cacahuete Frito

3
... więc stawiam na główny wniosek: singiel sizeofjest wyraźnie niebezpieczny (przyczyny są w odpowiedzi), i nie używa makr, ale korzysta z konstrukcji, które podałem za każdym razem, jest jeszcze bardziej niebezpieczny, więc jedyną drogą, aby przejść to makra.
Cacahuete Frito,

3
@MarkHarrison Znam różnicę między wskaźnikami i tablicami. Ale były czasy, kiedy miałem funkcję, którą później przekształciłem w małe funkcje, a to, co najpierw było tablicą, później było wskaźnikiem, i to jest jeden punkt, w którym jeśli zapomnisz zmienić rozmiar, wkręcisz go i łatwo nie widać jeden z tych.
Cacahuete Frito

3
@hyde Również, że wiem, że różnica nie oznacza, że ​​wszyscy ją znają, a dlaczego nie użyć czegoś, co w zasadzie usuwa 100% tych błędów? Ten błąd prawie dotarł do Linuksa; dotarł do Linusa, co oznacza, że ​​przeszedł wiele kontroli, a co za tym idzie, istnieje możliwość, że ten sam błąd dostał się do Linuksa w innej części jądra, jak sam mówi.
Cacahuete Frito,

11

„wprowadziłeś subtelny sposób strzelania sobie w stopę”

C Tablice „rodzime” nie przechowują swoich rozmiarów. Dlatego zaleca się zapisanie długości tablicy w osobnej zmiennej / const i przekazywanie jej za każdym razem, gdy przekazujesz tablicę, to znaczy:

#define MY_ARRAY_LENGTH   15
int myArray[MY_ARRAY_LENGTH];

NALEŻY zawsze unikać rodzimych tablic (chyba że nie możesz, w takim przypadku uważaj na swoją stopę). Jeśli piszesz w C ++, użyj kontenera „wektorowego” STL . „W porównaniu z tablicami zapewniają prawie taką samą wydajność” i są znacznie bardziej przydatne!

// 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();

Zobacz: http://www.cplusplus.com/reference/stl/vector/


Czytałem, że właściwym sposobem deklarowania stałych liczb całkowitych w C jest użycie enumdeklaracji.
Raffi Khatchadourian

Pytanie dotyczy C, a nie C ++. Więc nie ma STL.
Cacahuete Frito

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

6
Zauważ, że działa to tylko w przypadku rzeczywistych tablic, a nie wskaźników, które akurat wskazują na tablice.
David Schwartz

5

Jeśli naprawdę chcesz to zrobić, aby ominąć tablicę, sugeruję zaimplementowanie struktury do przechowywania wskaźnika do typu, którego chcesz tablicę, oraz liczby całkowitej reprezentującej rozmiar tablicy. Następnie możesz przekazać to swoim funkcjom. Po prostu przypisz wartość zmiennej tablicowej (wskaźnik do pierwszego elementu) do tego wskaźnika. Następnie możesz przejść Array.arr[i]do i-tego elementu i użyć, Array.sizeaby uzyskać liczbę elementów w tablicy.

Podałem ci trochę kodu. Nie jest to bardzo przydatne, ale można go rozszerzyć o więcej funkcji. Szczerze mówiąc, jeśli tego właśnie chcesz, powinieneś przestać używać C i używać innego języka z wbudowanymi funkcjami.

/* 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
Nie można upvote kodu, który strcpy(d.name, name);nie obsługuje przepełnienia.
chux - Przywróć Monikę

4

Najlepszym sposobem jest zapisanie tych informacji, na przykład w strukturze:

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

Wdrażaj wszystkie niezbędne funkcje, takie jak tworzenie, niszczenie, sprawdzanie równości i wszystko, czego potrzebujesz. Łatwiej jest przekazać jako parametr.


6
Jakikolwiek powód int elementsvs size_t elements?
chux - Przywróć Monikę

3

Funkcja sizeofzwraca liczbę bajtów używanych przez tablicę w pamięci. Jeśli chcesz obliczyć liczbę elementów w tablicy, powinieneś podzielić tę liczbę ze sizeofzmiennym typem tablicy. Powiedzmy, że int array[10];jeśli liczba całkowita typu zmiennego w twoim komputerze to 32 bity (lub 4 bajty), aby uzyskać rozmiar tablicy, wykonaj następujące czynności:

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

2

Możesz użyć &operatora. Oto kod źródłowy:

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

Oto przykładowy wynik

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

7
Nie głosowałem za głosem, ale to jest jak uderzanie gwoździa cegłą, ponieważ nie zauważyłeś leżącego obok siebie młota. Ponadto ludzie mają skłonność do marszczenia się za używanie niezainicjowanych zmiennych ... ale tutaj myślę, że służy to wystarczająco dobrze celowi.
Dmitri

2
@Dmitri nie są tu dostępne żadne niezainicjowane zmienne
MM

1
Hmmm. Odejmowanie wskaźnika prowadzi do ptrdiff_t. sizeof()powoduje w size_t. C nie określa, która jest szersza lub wyższa / ta sama ranga. Zatem rodzaj ilorazu ((char *)(&a+1)-(char *)a)/(sizeof(a[0]))nie jest na pewno, size_ta zatem drukowanie za pomocą zmoże prowadzić do UB. Wystarczy samo użycie printf("The size of array a is %zu\n", sizeof a/sizeof a[0]);.
chux - Przywróć Monikę

1
(char *)(&a+1)-(char *)anie jest stałą i może być obliczany w czasie wykonywania, nawet przy stałej wielkości a[10]. sizeof(a)/sizeof(a[0])w tym przypadku jest wykonywana stale w czasie kompilacji.
chux - Przywróć Monikę

1

Najprostsza odpowiedź:

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

1

Bardziej eleganckim rozwiązaniem będzie

size_t size = sizeof(a) / sizeof(*a);

0

Oprócz udzielonych już odpowiedzi chcę wskazać szczególny przypadek zastosowania

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

Jeśli ajest albo tablicą char, unsigned charalbo signed charnie trzeba używać sizeofdwa razy, ponieważ zawsze występuje sizeofwyrażenie z jednym operandem tego typu 1.

Cytat z C18,6.5.3.4 / 4:

Gdy sizeofzostanie zastosowany do operandu, który ma typ char, unsigned charlub signed char(lub jego kwalifikowaną wersję), wynikiem jest 1.”

Zatem sizeof(a) / sizeof (a[0])byłby równoważny z NUMBER OF ARRAY ELEMENTS / 1if, jeśli ajest tablicą typu char, unsigned charlub signed char. Podział na 1 jest zbędny.

W takim przypadku możesz po prostu skrócić i wykonać:

sizeof(a)

Na przykład:

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

Jeśli potrzebujesz dowodu, oto link do GodBolt .


Niemniej jednak dział utrzymuje bezpieczeństwo, jeśli typ znacznie się zmieni (chociaż przypadki te są rzadkie).


1
Prawdopodobnie wolisz nadal stosować makro z podziałem, ponieważ typ może się zmienić w przyszłości (choć być może mało prawdopodobne), a podział jest znany w czasie kompilacji, więc kompilator zoptymalizuje go (jeśli się nie zmieni twój kompilator).
Cacahuete Frito

1
@CacahueteFrito Tak, w międzyczasie też o tym myślałem. Wziąłem to jako dodatkową notatkę w odpowiedzi. Dziękuję Ci.
RobertS wspiera Monikę Cellio

-1

Uwaga: To może dać ci niezdefiniowane zachowanie wskazane przez MM w komentarzu.

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

Aby uzyskać więcej informacji, zobacz tutaj i również tutaj .


2
Jest to technicznie niezdefiniowane zachowanie; *operator nie może być stosowany do wskaźnika past-the-end
MM

3
„nieokreślone zachowanie” oznacza, że ​​Standard C nie definiuje zachowania. Jeśli wypróbujesz to w swoim programie, wszystko może się zdarzyć
MM

@MM, czy mówisz, że *(&a+1) - a;różni się od (&a)[1] - a;powyższego, czy nie oba *(&a+1)i (&a)[1]liczone jako 1 po zakończeniu?
QuentinUK

@QuentinUK twoje dwa wyrażenia są takie same, x[y]jest zdefiniowane jako*(x + (y))
MM

@MM Tak myślałem. Ale druga odpowiedź Arjun Sreedharan ma 38 strzał w górę, a to ma -1. W odpowiedzi Arjun Sreedharan nie ma wzmianki o nieokreślonym zachowaniu.
QuentinUK
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.