Jak znaleźć „sizeof” (wskaźnik wskazujący na tablicę)?

309

Po pierwsze, oto kod:

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

    return 0;
}

Czy istnieje sposób, aby dowiedzieć się, jaki rozmiar tablicy ptrwskazuje (zamiast podawać jej rozmiar, czyli cztery bajty w systemie 32-bitowym)?

jkidv
źródło
84
Zawsze używałem parens z sizeof - na pewno sprawia, że ​​wygląda jak wywołanie funkcji, ale myślę, że jest wyraźniejsze.
Paul Tomblin
20
Dlaczego nie? Czy masz coś przeciwko zbędnym nawiasom? Myślę, że sam z nimi czyta trochę łatwiej.
David Thornley
6
@Paul: cóż .. zakładając, że lewa strona tego wywołania jest wskaźnikiem do int, napisałbym to jako int * ptr = malloc (4 * sizeof * ptr); co dla mnie jest o wiele wyraźniejsze. Mniej pauz do czytania i przeniesienie dosłownej stałej na pierwszy plan, jak w matematyce.
zrelaksuj się
4
@unwind - nie alokuj tablicy wskaźników, gdy masz na myśli tablicę ints!
Paul Tomblin
6
Nie ma tutaj „wskaźnika wskazującego tablicę”. Tylko wskaźnik wskazujący na int.
newacct

Odpowiedzi:

269

Nie możesz. Kompilator nie wie, na co wskazuje wskaźnik. Istnieją sztuczki, takie jak zakończenie tablicy znaną wartością poza pasmem, a następnie zliczanie rozmiaru aż do tej wartości, ale to nie jest używane sizeof().

Kolejną sztuczką jest ta wspomniana przez Zana , która polega na tym, żeby gdzieś schować rozmiar. Na przykład, jeśli dynamicznie przydzielasz tablicę, przydziel blok o jeden int większy niż ten, którego potrzebujesz, ukryj rozmiar w pierwszym int i wróć ptr+1jako wskaźnik do tablicy. Kiedy potrzebujesz rozmiaru, zmniejsz wskaźnik i zajrzyj do wartości ukrytej. Pamiętaj tylko, aby uwolnić cały blok od początku, a nie tylko tablicę.

Paul Tomblin
źródło
12
Przepraszam, że opublikowałem komentarz tak późno, ale jeśli kompilator nie wie, co wskazuje wskaźnik, w jaki sposób wolna wie, ile pamięci należy wyczyścić? Wiem, że ta informacja jest przechowywana wewnętrznie dla funkcji takich jak darmowy. Moje pytanie brzmi: dlaczego kompilator może to zrobić?
viki.omega9
11
@ viki.omega9, ponieważ free odkrywa rozmiar w czasie wykonywania. Kompilator nie może znać rozmiaru, ponieważ można ustawić inną wielkość tablicy w zależności od czynników uruchomieniowych (argumenty wiersza poleceń, zawartość pliku, faza księżyca itp.).
Paul Tomblin
15
Szybkie śledzenie, dlaczego nie ma funkcji, która może przywrócić rozmiar tak, jak robi to za darmo?
viki.omega9
5
Cóż, jeśli możesz zagwarantować, że funkcja została wywołana tylko z pamięcią malloced, a biblioteka śledzi pamięć malloced w sposób, jaki najbardziej widziałem (używając int przed zwróconym wskaźnikiem), możesz napisać jedną. Ale jeśli wskaźnik odnosi się do tablicy statycznej lub podobnej, zawiedzie. Podobnie, nie ma gwarancji, że rozmiar malloced pamięci jest dostępny dla twojego programu.
Paul Tomblin
9
@ viki.omega9: Inną rzeczą, o której należy pamiętać, jest to, że rozmiar zarejestrowany przez system malloc / free może nie być wielkością, o którą prosiłeś. Możesz malloc 9 bajtów i dostajesz 16. Malloc 3K bajtów i dostajesz 4K. Lub podobne sytuacje.
Zan Lynx,
85

Odpowiedź brzmi nie."

Programiści C przechowują gdzieś rozmiar tablicy. Może być częścią struktury lub programista może oszukiwać nieco malloc()więcej pamięci niż jest to wymagane w celu przechowywania wartości długości przed rozpoczęciem tablicy.

Zan Lynx
źródło
3
Tak implementowane są łańcuchy pascal
dsm
6
i najwyraźniej pascal strings są powodem, dla którego excel działa tak szybko!
Adam Naylor
8
@Adam: Jest szybki. Używam go w mojej liście ciągów implementacji. Jest superszybki w wyszukiwaniu liniowym, ponieważ jest to: ładuj rozmiar, pos pos + rozmiar, porównaj rozmiar do rozmiaru wyszukiwania, jeśli równa strncmp, przejdź do następnego ciągu, powtórz. Jest szybszy niż wyszukiwanie binarne do około 500 ciągów.
Zan Lynx,
47

W przypadku tablic dynamicznych ( malloc lub C ++ nowy ) musisz przechowywać rozmiar tablicy, jak wspomniano przez innych, lub być może zbudować strukturę menedżera tablic, która obsługuje dodawanie, usuwanie, liczenie itp. Niestety C nie robi tego prawie tak dobrze, jak C ++, ponieważ zasadniczo musisz go zbudować dla każdego rodzaju przechowywanego typu tablicy, co jest uciążliwe, jeśli masz wiele typów tablic, którymi musisz zarządzać.

W przypadku tablic statycznych, takich jak ten w twoim przykładzie, do uzyskania rozmiaru używa się wspólnego makra, ale nie jest to zalecane, ponieważ nie sprawdza, czy parametr jest rzeczywiście tablicą statyczną. Makro jest używane w prawdziwym kodzie, np. W nagłówkach jądra Linuksa, chociaż może się nieco różnić od poniższego:

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

Możesz wyszukiwać w Google z powodów, aby uważać na takie makra. Bądź ostrożny.

Jeśli to możliwe, stdlib C ++, taki jak wektor, jest znacznie bezpieczniejszy i łatwiejszy w użyciu.

Ryan
źródło
11
ARRAY_SIZE to powszechny paradygmat stosowany przez praktycznych programistów na całym świecie.
Sanjaya R
5
Tak, to powszechny paradygmat. Nadal musisz go używać ostrożnie, ponieważ łatwo go zapomnieć i użyć go na dynamicznej tablicy.
Ryan
2
Tak, dobra uwaga, ale zadawano pytanie o wskaźnik, a nie o matrycę statyczną.
Paul Tomblin
2
To ARRAY_SIZEmakro zawsze działa, jeśli jego argumentem jest tablica (tzn. Wyrażenie typu tablicowego). W przypadku tak zwanej „tablicy dynamicznej” nigdy nie otrzymasz rzeczywistej „tablicy” (wyrażenie typu tablicy). (Oczywiście nie możesz, ponieważ typy tablic uwzględniają ich rozmiar w czasie kompilacji.) Po prostu dostajesz wskaźnik do pierwszego elementu. Twój sprzeciw „nie sprawdza, czy parametr jest rzeczywiście tablicą statyczną”, nie jest tak naprawdę ważny, ponieważ są one różne, ponieważ jedna jest tablicą, a druga nie.
newacct
2
Wokół unosi się funkcja szablonu, która robi to samo, ale uniemożliwi użycie wskaźników.
Natalie Adams
18

Istnieje czyste rozwiązanie z szablonami C ++, bez użycia sizeof () . Następująca funkcja getSize () zwraca rozmiar dowolnej tablicy statycznej:

#include <cstddef>

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

Oto przykład ze 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;
}

Wynik:

3
5
Skurton
źródło
Nigdy nie widziałem notacji T (&)[SIZE]. Czy możesz wyjaśnić, co to oznacza? Możesz również wspomnieć constexpr w tym kontekście.
WorldSEnder
2
To miło, jeśli używasz c ++ i faktycznie masz zmienną typu tablicowego. Żadna z nich nie ma miejsca w pytaniu: językiem jest C, a rzeczą, którą OP chce uzyskać rozmiar tablicy, jest prosty wskaźnik.
Oguk
czy ten kod doprowadziłby do rozdęcia kodu poprzez odtworzenie tego samego kodu dla każdej kombinacji rozmiaru / typu, czy też jest to magicznie zoptymalizowany brak kompilatora?
user2796283,
@WorldSEnder: To jest składnia C ++ dla odwołania do typu tablicy (bez nazwy zmiennej, tylko rozmiar i typ elementu).
Peter Cordes,
@ user2796283: Ta funkcja jest całkowicie zoptymalizowana w czasie kompilacji; nie jest potrzebna magia; nie łączy niczego w jedną definicję, po prostu łączy ją ze stałą czasową kompilacji. (Ale w wersji do debugowania, tak, masz kilka osobnych funkcji, które zwracają różne stałe. Magia linkera może łączyć te, które używają tej samej stałej. Wywoływacz nie przechodzi SIZEjako argument, to parametr szablonu, który ma być znane z definicji funkcji.)
Peter Cordes,
5

W tym konkretnym przykładzie tak jest, JEŻELI używasz typedefs (patrz poniżej). Oczywiście, jeśli zrobisz to w ten sposób, równie dobrze możesz użyć SIZEOF_DAYS, ponieważ wiesz, na co wskazuje wskaźnik.

Jeśli masz wskaźnik (void *), który jest zwracany przez malloc () lub tym podobne, to nie, nie ma sposobu na określenie struktury danych, na którą wskazuje wskaźnik, a zatem nie ma sposobu na określenie jego rozmiaru.

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

Wynik:

SIZEOF_DAYS:  20
sizeof(days): 20
sizeof(*ptr): 20
sizeof(ptr):  4
David
źródło
5

Jak podają wszystkie poprawne odpowiedzi, nie można uzyskać tej informacji z samej zepsutej wartości wskaźnika tablicy. Jeśli zanikający wskaźnik jest argumentem odebranym przez funkcję, to rozmiar tablicy inicjującej musi być podany w inny sposób, aby funkcja mogła poznać ten rozmiar.

Oto sugestia inna niż dotychczas, która zadziała: zamiast tego przekaż wskaźnik do tablicy. Ta sugestia jest podobna do sugestii w stylu C ++, z tym wyjątkiem, że C nie obsługuje szablonów ani referencji:

#define ARRAY_SZ 10

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

Ale ta sugestia jest trochę głupia dla twojego problemu, ponieważ funkcja jest zdefiniowana tak, aby dokładnie znać rozmiar przekazywanej tablicy (stąd nie ma potrzeby używania sizeof w ogóle na tablicy). Jednak zapewnia pewne bezpieczeństwo. Pozwoli ci to na przejście w szeregu niechcianych rozmiarów.

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

Jeśli funkcja ma być zdolna do działania na dowolnym rozmiarze tablicy, musisz podać jej rozmiar jako dodatkową informację.

jxh
źródło
1
+1 dla „Nie można uzyskać tej informacji na podstawie zepsutej wartości wskaźnika samej tablicy” i udostępnienie obejścia.
Maks.
4

Nie ma magicznego rozwiązania. C nie jest językiem refleksyjnym. Obiekty nie wiedzą automatycznie, jakie są.

Ale masz wiele możliwości:

  1. Oczywiście dodaj parametr
  2. Zawiń połączenie w makro i automatycznie dodaj parametr
  3. Użyj bardziej złożonego obiektu. Zdefiniuj strukturę, która zawiera tablicę dynamiczną, a także rozmiar tablicy. Następnie podaj adres struktury.
DigitalRoss
źródło
Przedmioty wiedzą, jakie są. Ale jeśli wskażesz na podobiekt, nie ma możliwości uzyskania informacji o całym obiekcie lub większym podobiektie
MM
2

Moim rozwiązaniem tego problemu jest zapisanie długości tablicy w tablicy Array jako meta-informacji o tablicy.

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

Ale musisz zadbać o ustawienie odpowiedniej długości tablicy, którą chcesz przechowywać, ponieważ nie ma możliwości sprawdzenia tej długości, jak masowo wyjaśnili nasi przyjaciele.


źródło
2

Możesz zrobić coś takiego:

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ᴏʟʙᴇʀɢ
źródło
1

Nie, nie można użyć sizeof(ptr)do znalezienia rozmiaru tablicy, na którą ptrwskazuje.

Chociaż przydzielenie dodatkowej pamięci (większej niż rozmiar tablicy) będzie pomocne, jeśli chcesz przechowywać długość w dodatkowej przestrzeni.

SKD
źródło
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;
}

Rozmiar dni [] wynosi 20, co nie jest liczbą elementów * rozmiar typu danych. Podczas gdy rozmiar wskaźnika wynosi 4 bez względu na to, na co wskazuje. Ponieważ wskaźnik wskazuje na inny element, przechowując jego adres.

Shivangi Chaurasia
źródło
1
sizeof (ptr) to rozmiar wskaźnika, a sizeof (* ptr) to rozmiar wskaźnika, do którego
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 przekazuje do zmiennej 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

Zastosowanie to:

void main() {

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

        array1.array[i] *= 2;
    }
}
użytkownik3065147
źródło
0

W łańcuchach '\0'na końcu znajduje się znak, więc długość łańcucha można uzyskać za pomocą funkcji takich jak strlen. Na przykład problem z tablicą liczb całkowitych polega na tym, że nie można użyć żadnej wartości jako wartości końcowej, więc jednym z możliwych rozwiązań jest zaadresowanie tablicy i użycie jako wartości końcowej NULLwskaźnika.

#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
źródło
Twój kod jest pełen komentarzy, ale myślę, że ułatwiłoby to wszystko, gdybyś dodał ogólne wyjaśnienie, jak to działa poza kodem, jak zwykły tekst. Czy możesz edytować swoje pytanie i zrobić to? Dziękuję Ci!
Fabio mówi Przywróć Monikę
Utworzenie tablicy wskaźników dla każdego elementu, aby można było ją przeszukać liniowo, NULLjest prawdopodobnie najmniej wydajną alternatywą, jaką można sobie wyobrazić, aby przechowywać osobno sizebezpośrednio. Zwłaszcza jeśli cały czas korzystasz z tej dodatkowej warstwy pośredniej.
Peter Cordes,