Как найти «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-битной системе)?

jkidv
источник
84
Я всегда использовал парены с sizeof - конечно, это выглядит как вызов функции, но я думаю, что это понятнее.
Пол Томблин
20
Почему нет? Есть ли у вас что-то против лишних скобок? Я думаю, что с ними мне легче читать.
Дэвид Торнли
6
@Paul: хорошо ... если предположить, что левая часть этого вызова является указателем на int, я бы написал это как int * ptr = malloc (4 * sizeof * ptr); что для меня гораздо понятнее. Меньше паренсов, чтобы читать и выводить буквальный констант, как в математике.
расслабиться
4
@unwind - не выделяйте массив указателей, когда вы имели в виду массив целых чисел!
Пол Томблин
6
Здесь нет указателя на массив. Просто указатель, указывающий на int.
newacct

Ответы:

269

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

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

Пол Томблин
источник
12
Извините за столь позднюю публикацию комментария, но если компилятор не знает, на что указывает указатель, как free узнает, сколько памяти нужно очистить? Я знаю, что эта информация хранится внутри для функций, таких как бесплатное использование. Поэтому мой вопрос: почему компилятор тоже может это делать?
viki.omega9
11
@ viki.omega9, потому что free обнаруживает размер во время выполнения. Компилятор не может знать размер, потому что вы можете сделать массив другого размера в зависимости от факторов времени выполнения (аргументы командной строки, содержимое файла, фаза луны и т. Д.).
Пол Томблин
15
Быстрое наблюдение, почему нет функции, которая может вернуть размер, как это делает free?
viki.omega9
5
Что ж, если вы можете гарантировать, что функция вызывается только с недопустимой памятью, а библиотека отслеживает недопустимую память так, как это делает большинство из них (используя int перед возвращаемым указателем), вы можете написать ее. Но если указатель на статический массив или тому подобное, он потерпит неудачу. Точно так же нет никакой гарантии, что размер ошибочной памяти будет доступен для вашей программы.
Пол Томблин
9
@ viki.omega9: Следует также помнить, что размер, записанный системой malloc / free, может не соответствовать запрашиваемому вами размеру. Вы Malloc 9 байтов и получите 16. Malloc 3K байтов и получите 4K. Или похожие ситуации.
Zan Lynx
85

Ответ - нет."

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

Зан Рысь
источник
3
Вот как реализованы паскальские строки
DSM
6
и очевидно паскальские струны - вот почему Excel работает так быстро!
Адам Нейлор
8
@ Адам: это быстро. Я использую его в списке моей реализации строк. Это очень быстрый линейный поиск, потому что это: размер загрузки, предварительная выборка 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;
}

Вы можете гуглить по причинам, опасающимся подобных макросов. Быть осторожен.

Если возможно, C ++ stdlib, такой как vector, намного безопаснее и проще в использовании.

Райан
источник
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
skurton
источник
Я никогда не видел обозначений T (&)[SIZE]. Можете ли вы объяснить, что это значит? Также вы можете упомянуть constexpr в этом контексте.
WorldSEnder
2
Это хорошо, если вы используете c ++ и у вас действительно есть переменная типа массива. Ни один из них не относится к вопросу: язык - это C, и OP хочет получить размер массива - простой указатель.
Огук
приведет ли этот код к раздутости кода, воссоздав один и тот же код для каждой комбинации размера / типа, или это волшебным образом оптимизировано компилятором?
user2796283
@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 */

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

jxh
источник
1
+1 за «Вы не можете получить эту информацию из затухшего значения указателя одного массива» и предоставляя обходной путь.
Макс
4

Там нет волшебного решения. С не является рефлексивным языком. Объекты не знают автоматически, что они есть.

Но у вас есть много вариантов:

  1. Очевидно, добавить параметр
  2. Обернуть вызов в макрос и автоматически добавить параметр
  3. Используйте более сложный объект. Определите структуру, которая содержит динамический массив, а также размер массива. Затем передайте адрес структуры.
DigitalRoss
источник
Объекты знают, что они есть. Но если вы укажете на подобъект, нет способа получить информацию о полном объекте или более крупном подобъекте
ММ
2

Мое решение этой проблемы состоит в том, чтобы сохранить длину массива в struct Array в качестве мета-информации о массиве.

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

struct Array
{
    int length;

    double *array;
};

typedef struct Array Array;

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

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

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

    return newArray;
}

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

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

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

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

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

    PrintArray(days);

    SetArray(days,10,moreDays);

    PrintArray(days);

    negativeTest = NewArray(-5);

    PrintArray(negativeTest);

    return 0;
}

Но вам нужно позаботиться о том, чтобы установить правильную длину массива, который вы хотите сохранить, потому что нет способа проверить эту длину, как объяснили наши друзья.


источник
2

Вы можете сделать что-то вроде этого:

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ᴏʟʙᴇʀɢ
источник
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;
    }
}
user3065147
источник
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 );
}
Baz
источник
Ваш код полон комментариев, но я думаю, что было бы проще, если бы вы добавили общее объяснение того, как это работает вне кода, как обычный текст. Можете ли вы отредактировать свой вопрос и сделать это? Спасибо!
Фабио говорит восстановить Монику
Создание массива указателей на каждый элемент, чтобы вы могли осуществлять линейный поиск по нему, NULLявляется, вероятно, наименее эффективной альтернативой, которую можно только представить для хранения отдельного элемента size. Особенно, если вы действительно используете этот дополнительный слой косвенности все время.
Питер Кордес