Як знайти 'sizeof' (вказівник, що вказує на масив)?


309

По-перше, ось код:

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

    return 0;
}

Чи є спосіб дізнатися розмір масиву, на який ptrвказує (замість того, щоб просто вказати його розмір, який становить чотири байти в 32-бітовій системі)?


84
Я завжди використовував паролі з sizeof - впевнений, що це виглядає як виклик функції, але я думаю, що це зрозуміліше.
Пол Томблін

20
Чому ні? Чи є щось проти зайвих дужок? Я думаю, що це читається з ними трохи легше.
Девід Торнлі

6
@Paul: добре .. якщо припустити, що ліва частина цього виклику є вказівником на int, я б написав його як int * ptr = malloc (4 * sizeof * ptr); що мені набагато зрозуміліше. Менше парень для читання і виведення буквального константа на фронт, як у математиці.
розмотується

4
@unwind - не виділяйте масив покажчиків, коли ви мали на увазі масив ints!
Пол Томблін

6
Тут немає "вказівника, що вказує на масив". Просто вказівник, що вказує на int.
newacct

Відповіді:


269

Ні, ти не можеш. Компілятор не знає, на що вказує вказівник. Існують хитрощі, як закінчення масиву з відомим значенням поза діапазону, а потім підрахунок розміру до цього значення, але це не використовується sizeof().

Ще одна хитрість - це згадана Заном , яка полягає в тому, щоб десь сховати розміри. Наприклад, якщо ви динамічно розподіляєте масив, виділіть блок на один int більший за потрібний, збережіть розмір у першому int та поверніться ptr+1як вказівник на масив. Коли вам потрібен розмір, зменшіть вказівник та загляньте на сховане значення. Просто пам’ятайте, щоб звільнити весь блок, починаючи з початку, а не лише масив.


12
Вибачте за те, що виклали коментар так пізно, але якщо компілятор не знає, на що вказує вказівник, як вільний знає, скільки пам'яті потрібно очистити? Я знаю, що ця інформація зберігається всередині таких функцій, як безкоштовна у використанні. Отже, моє запитання: чому може зробити це і компілятор?
viki.omega9

11
@ viki.omega9, оскільки безкоштовно виявляє розмір під час виконання. Компілятор не може знати розмір, тому що ви можете зробити масив різного розміру залежно від факторів виконання (аргументи командного рядка, вміст файлу, фаза місяця тощо).
Пол Томблін

15
Швидке спостереження, чому не існує функції, яка може повернути розмір так само, як безкоштовний?
viki.omega9

5
Ну, якщо ви могли гарантувати, що функція викликається лише з неправильною пам'яттю, і бібліотека відслідковує неправильну пам'ять так, як це робило більшість, що я бачив (використовуючи int перед повернутим вказівником), то ви можете її написати. Але якщо вказівник буде статичним масивом чи подібним, він не зможе. Так само немає гарантії того, що розмір заблокованої пам'яті буде доступний вашій програмі.
Пол Томблін

9
@ viki.omega9: Ще одна річ, яку потрібно пам’ятати, - це те, що розмір, записаний системою malloc / free, може бути не той розмір, про який ви просили. Ви виділяєте 9 байт і отримуєте 16 байт Malloc 3K і отримуєте 4K. Або подібні ситуації.
Zan Lynx

85

Відповідь - "Ні".

Програмісти C роблять десь зберігати розмір масиву. Це може бути частиною структури, або програміст може обдурити трохи і malloc()більше пам'яті, ніж вимагається, щоб зберегти значення довжини до початку масиву.


3
Ось як реалізуються pascal рядки
dsm

6
і, мабуть, паскальні струни - чому excel працює так швидко!
Адам Нейлор

8
@Adam: Це швидко. Я використовую його у списку моїх рядків. Лінійний пошук дуже швидкий, оскільки це: розмір завантаження, попередній вибір pos + size, порівняння розміру з розміром пошуку, якщо рівний strncmp, перехід до наступного рядка, повторення. Це швидше, ніж двійковий пошук до приблизно 500 рядків.
Zan Lynx

47

Для динамічних масивів ( malloc або C ++ new ) вам потрібно зберегти розмір масиву, як згадують інші, або, можливо, побудувати структуру менеджера масиву, яка обробляє додавання, видалення, підрахунок тощо. На жаль, C не робить це майже так само добре, як C ++, оскільки ви в основному повинні будувати його для кожного іншого типу масиву, який ви зберігаєте, що є громіздким, якщо у вас є кілька типів масивів, якими потрібно керувати.

Для статичних масивів, наприклад, у вашому прикладі, є загальний макрос, який використовується для отримання розміру, але він не рекомендується, оскільки він не перевіряє, чи справді параметр є статичним масивом. Макрос використовується в реальному коді, хоча, наприклад, в заголовках ядра Linux, хоча він може бути дещо іншим, ніж наведений нижче:

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

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

Ви можете в google з причин насторожено ставитися до таких макросів. Будь обережний.

Якщо це можливо, C ++ stdlib, такий як вектор, який набагато безпечніший і простіший у використанні.


11
ARRAY_SIZE - поширена парадигма, яку практичні програмісти використовують скрізь.
Санджая Р

5
Так, це загальна парадигма. Використовувати його потрібно обережно, хоча легко забути і використовувати його в динамічному масиві.
Райан

2
Так, добре, але запитання, яке задавали, стосувалося вказівного, а не статичного масиву.
Пол Томблін

2
Цей ARRAY_SIZEмакрос завжди працює, якщо його аргументом є масив (тобто вираз типу масиву). Для вашого так званого "динамічного масиву" ви ніколи не отримуєте власне "масив" (вираз типу масиву). (Звичайно, ви не можете, оскільки типи масивів включають їх розмір під час компіляції.) Ви просто отримуєте вказівник на перший елемент. Ваше заперечення "не перевіряє, чи є параметр дійсно статичним масивом" насправді не є дійсним, оскільки вони різні, оскільки один - це масив, а інший - ні.
newacct

2
Існує функція шаблону, що плаває навколо, що робить те ж саме, але запобігає використанню покажчиків.
Наталі Адамс

18

Існує чисте рішення з шаблонами C ++, не використовуючи sizeof () . Наступна функція getSize () повертає розмір будь-якого статичного масиву:

#include <cstddef>

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

Ось приклад зі структурою foo_t :

#include <cstddef>

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

struct foo_t {
    int ball;
};

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

    return 0;
}

Вихід:

3
5

Я ніколи не бачив позначень T (&)[SIZE]. Чи можете ви пояснити, що це означає? Також у цьому контексті можна згадати constexpr.
WorldSEnder

2
Це добре, якщо ви використовуєте c ++ і у вас фактично є змінна типу масиву. Жоден із них не стосується питання: Мова - це C, і те, що ОП хоче отримати розмір масиву - це простий покажчик.
Огук

чи призведе цей код до роздуття коду шляхом відтворення одного і того ж коду для кожної різної комбінації розмірів / типів, чи це магічно оптимізовано поза існуванням компілятором?
користувач2796283

@WorldSEnder: Це синтаксис C ++ для посилання типу масиву (без назви змінної, просто розмір і тип елемента).
Пітер Кордес

@ user2796283: Ця функція повністю оптимізована під час компіляції; магія не потрібна; це не поєднувати нічого з одним визначенням, це просто присвоєння його константі часу компіляції. (Але у складі налагодження, так, у вас буде купа окремих функцій, які повертають різні константи. Магія лінкера може об'єднати ті, які використовують ту саму константу. Абонент не переходить SIZEяк аргумент, це параметр шаблону, який має вже відоме за визначенням функції.)
Пітер Кордес,

5

Для цього конкретного прикладу, так, є, ЯКЩО ви використовуєте typedefs (див. Нижче). Звичайно, якщо ви зробите це таким чином, ви так само добре використовуєте SIZEOF_DAYS, оскільки знаєте, на що вказує вказівник.

Якщо у вас є вказівник (void *), як повертається malloc () або подібне, то ні, немає способу визначити, на яку структуру даних вказівник вказує, і, таким чином, не можна визначити його розмір.

#include <stdio.h>

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

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

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

    return 0;
} 

Вихід:

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

5

Як було сказано у всіх правильних відповідях, ви не можете отримати цю інформацію тільки зі зменшеного значення вказівника масиву. Якщо розкладений покажчик є аргументом, отриманим функцією, то розмір вихідного масиву повинен бути наданий якимось іншим способом, щоб функція дізналася про цей розмір.

Ось пропозиція, відмінна від того, що було надано дотепер, і це спрацює: замість цього переведіть покажчик на масив. Ця пропозиція схожа на пропозиції C ++, за винятком того, що C не підтримує шаблони чи посилання:

#define ARRAY_SZ 10

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

Але ця пропозиція є якось нерозумною для вашої проблеми, оскільки функція визначена, щоб точно знати розмір масиву, який передається (отже, у масиві зовсім не потрібно використовувати sizeof). Однак це може забезпечити безпеку певного типу. Це забороняє вам пропускати масив небажаного розміру.

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

Якщо функція повинна працювати в будь-якому розмірі масиву, вам доведеться надати розмір функції як додаткову інформацію.


1
+1 для "Ви не можете отримати цю інформацію тільки зі зменшеним значенням вказівника масиву" і забезпечити обхід.
Макс

4

Немає магічного рішення. С не є рефлексивною мовою. Об'єкти автоматично не знають, що вони є.

Але у вас є багато варіантів:

  1. Очевидно, додайте параметр
  2. Оберніть виклик в макрос і автоматично додайте параметр
  3. Використовуйте більш складний об’єкт. Визначте структуру, яка містить динамічний масив, а також розмір масиву. Потім передайте адресу структури.

Об’єкти знають, що вони є. Але якщо ви вказуєте на субект, немає можливості отримати інформацію про повний об'єкт або про більший субобект
MM

2

Моє рішення цієї проблеми полягає в збереженні довжини масиву в структурний масив як метаінформація про масив.

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

struct Array
{
    int length;

    double *array;
};

typedef struct Array Array;

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

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

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

    return newArray;
}

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

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

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

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

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

    PrintArray(days);

    SetArray(days,10,moreDays);

    PrintArray(days);

    negativeTest = NewArray(-5);

    PrintArray(negativeTest);

    return 0;
}

Але вам потрібно подбати про те, щоб встановити правильну довжину масиву, який ви хочете зберігати, тому що це неможливо перевірити цю довжину, як масово пояснювали наші друзі.


2

Ви можете зробити щось подібне:

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

1

Ні, ви не можете використовувати, sizeof(ptr)щоб знайти розмір масиву, на ptrякий вказує.

Хоча виділення додаткової пам'яті (більше, ніж розмір масиву) буде корисним, якщо ви хочете зберегти довжину в додатковому просторі.


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

    return 0;
}

Розмір днів [] становить 20, що не містить елементів * розмір його типу даних. Хоча розмір вказівника дорівнює 4, незалежно від того, на що він вказує. Оскільки вказівник вказує на інший елемент, зберігаючи його адресу.


1
sizeof (ptr) - розмір вказівника, а sizeof (* ptr) - розмір вказівника, на який
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 переходить до змінної розміру :

#define array_size 30

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

#undef array_size

Використання:

void main() {

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

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

0

У рядках є '\0'символ в кінці, тому довжину рядка можна отримати за допомогою функцій типу strlen. Наприклад, проблема з цілим масивом, наприклад, полягає в тому, що ви не можете використовувати жодне значення як кінцеве значення, тому одне можливе рішення - адресувати масив і використовувати як кінцеве значення NULLвказівник.

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

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

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

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

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

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

Ваш код повний коментарів, але я думаю, що все було б простіше, якби ви додали якесь загальне пояснення, як це працює поза кодом, як звичайний текст. Чи можете ви відредагувати своє запитання і зробити це? Дякую!
Фабіо каже, що поверніть Моніку

Створення масиву покажчиків на кожен елемент, щоб ви могли лінійно шукати його NULL, мабуть, найменш ефективна альтернатива, яку можна уявити просто зберігати окремий sizeбезпосередньо. Особливо , якщо ви на самому справі використовувати цей додатковий шар побічно весь час.
Пітер Кордес
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.