Вычисление размера массива

Я использую следующий макрос для вычисления размера массива:

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

Однако я вижу несоответствие в значении, вычисленном им, когда я оцениваю размер массива в функции (неверное вычисляемое значение), в отличие от того, где вызывается функция (правильное значение вычисляется). Код + вывод ниже. Любые мысли, предложения, советы и др. приветствуется.

ДП

#include <stdio.h>

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

void foo(int * arr) // Also tried foo(int arr[]), foo(int * & arr) 
                    // - neither of which worked
{
   printf("arr : %x\n", arr);
   printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr));
}

int main()
{
   int arr[] = {1, 2, 3, 4};

   printf("arr : %x\n", arr);
   printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr));

   foo(arr);
}

Вывод:

arr : bffffa40
sizeof arr: 4
arr : bffffa40
sizeof arr: 1

Ответы

Ответ 1

Это потому, что размер int * - это размер указателя int (4 или 8 байтов на современных платформах, которые я использую, но он полностью зависит от платформы). sizeof рассчитывается во время компиляции, а не во время выполнения, поэтому даже sizeof (arr[]) не поможет, потому что вы можете вызвать функцию foo() во время выполнения со множеством массивов разных размеров.

Размер массива int - это размер массива int.

Это один из сложнейших битов в C/С++ - использование массивов и указателей не всегда одинаково. Массивы при очень многих обстоятельствах распадаются на указатель на первый элемент этого массива.

Существует как минимум два решения, совместимые как с C, так и с С++:

  • передать длину в массив (не это полезно, если цель функции состоит в том, чтобы фактически определить размер массива).
  • передать значение счетчика, обозначающее конец данных, например {1,2,3,4,-1}.

Ответ 2

Это не работает, потому что sizeof вычисляется во время компиляции. Функция не имеет информации о размере своего параметра (она знает только, что указывает на адрес памяти).

Вместо этого используйте STL-вектор или передайте размеры массива в качестве параметров для функций.

Ответ 3

В С++ вы можете определить G_N_ELEMENTS следующим образом:

template<typename T, size_t N> 
size_t G_N_ELEMENTS( T (&array)[N] )
{
  return N;
}

Если вы хотите использовать размер массива во время компиляции, вот как:

// ArraySize
template<typename T> 
struct ArraySize;

template<typename T, size_t N> 
struct ArraySize<T[N]> 
{ 
  enum{ value = N };
};

Спасибо j_random_hacker за исправление моих ошибок и предоставление дополнительной информации.

Ответ 4

Обратите внимание, что даже если вы попытаетесь рассказать компилятору C размер массива в функции, он не принимает подсказки (мой DIM эквивалентен вашему G_N_ELEMENTS):

#include <stdio.h>

#define DIM(x)  (sizeof(x)/sizeof(*(x)))

static void function(int array1[], int array2[4])
{
    printf("array1: size = %u\n", (unsigned)DIM(array1));
    printf("array2: size = %u\n", (unsigned)DIM(array2));
}

int main(void)
{
    int a1[40];
    int a2[4];
    function(a1, a2);
    return(0);
}

Отпечатки:

array1: size = 1
array2: size = 1

Если вы хотите знать, насколько большой массив находится внутри функции, передайте размер функции. Или, в С++, используйте такие вещи, как STL vector<int>.

Ответ 5

Изменить: С++ 11 был введен с момента написания этого ответа, и он включает в себя функции, которые можно выполнить именно так, как показано ниже: std::begin и std::end. Константные версии std::cbegin и std::cend также входят в будущую версию стандарта (С++ 14?) И могут быть уже в вашем компиляторе. Даже не рассматривайте возможность использования моих функций ниже, если у вас есть доступ к стандартным функциям.


Я хотел бы немного построить ответ Benoît.

Вместо того, чтобы пропустить только начальный адрес массива в качестве указателя или указатель плюс размер, как предполагали другие, возьмите реплику из стандартной библиотеки и передайте два указателя в начало и конец массива. Это не только делает ваш код более похожим на современный С++, но вы можете использовать любой из стандартных алгоритмов библиотеки в вашем массиве!

template<typename T, int N>
T * BEGIN(T (& array)[N])
{
    return &array[0];
}

template<typename T, int N>
T * END(T (& array)[N])
{
    return &array[N];
}

template<typename T, int N>
const T * BEGIN_CONST(const T (& array)[N])
{
    return &array[0];
}

template<typename T, int N>
const T * END_CONST(const T (& array)[N])
{
    return &array[N];
}

void
foo(int * begin, int * end)
{
  printf("arr : %x\n", begin);
  printf ("sizeof arr: %d\n", end - begin);
}

int
main()
{
  int arr[] = {1, 2, 3, 4};

  printf("arr : %x\n", arr);
  printf ("sizeof arr: %d\n", END(arr) - BEGIN(arr));

  foo(BEGIN(arr), END(arr));
}

Здесь альтернативное определение для BEGIN и END, если шаблоны не работают.

#define BEGIN(array) array
#define END(array) (array + sizeof(array)/sizeof(array[0]))

Обновление: Вышеприведенный код с шаблонами работает в MS VС++ 2005 и GCC 3.4.6, как и следовало ожидать. Мне нужно получить новый компилятор.

Я также пересматриваю соглашение об именах, используемое здесь. Функции шаблонов маскируются, поскольку макросы просто чувствуют себя не так. Я уверен, что скоро буду использовать это в своем собственном коде, и я думаю, что буду использовать ArrayBegin, ArrayEnd, ArrayConstBegin и ArrayConstEnd.

Ответ 6

Если вы измените foo funciton немного, это может заставить вас чувствовать себя немного более комфортно:

void foo(int * pointertofoo) 
{              
   printf("pointertofoo : %x\n", pointertofoo);  
   printf ("sizeof pointertofoo: %d\n", G_N_ELEMENTS(pointertofoo));
}

Что компилятор увидит что-то совершенно иное, чем функция.

Ответ 7

foo(int * arr) //Also tried foo(int arr[]), foo(int * & arr) 
{              // - neither of which worked
  printf("arr : %x\n", arr);
  printf ("sizeof arr: %d\n", G_N_ELEMENTS(arr));
}

sizeof (arr) - sizeof (int *), т.е. 4

Если у вас нет веской причины писать такой код, НЕ ДЕЛАЙТЕ. Сейчас мы в 21 веке, вместо этого используем std::vector.

Для получения дополнительной информации см. часто задаваемые вопросы по С++: http://www.parashift.com/c++-faq-lite/containers.html

Помните: "Массивы злы"

Ответ 8

Вы должны только вызывать sizeof для массива. Когда вы вызываете sizeof по типу указателя, размер будет всегда 4 (или 8 или независимо от вашей системы).

MSFT Венгерская нотация может быть уродливой, но если вы ее используете, вы не можете называть свой макрос ничем, начинающимся с "p".

Также проверьте определение макроса ARRAYSIZE() в WinNT.h. Если вы используете С++, вы можете делать странные вещи с шаблонами, чтобы получить время компиляции, если это так.

Ответ 9

Теперь, когда у нас есть constexpr в С++ 11, версия типа safe (non-macro) также может использоваться в постоянном выражении.

template<typename T, std::size_t size>
constexpr std::size_t array_size(T const (&)[size]) { return size; }

Это не скомпилирует, где он не работает должным образом, в отличие от вашего макро решения (он не будет работать с указателями случайно). Вы можете использовать его там, где требуется постоянная времени компиляции:

int new_array[array_size(some_other_array)];

При этом вам лучше использовать std::array для этого, если это возможно. Не обращайте внимания на людей, которые говорят использовать std::vector, потому что это лучше. std::vector - это другая структура данных с различными сильными сторонами. std::array не имеет накладных расходов по сравнению с массивом C-стиля, но в отличие от массива C-стиля он не будет распадаться на указатель при малейшей провокации. std::vector, с другой стороны, требует, чтобы все обращения являлись косвенными доступами (проходили через указатель), и для этого требуется динамическое распределение. Одна вещь, о которой следует помнить, если вы привыкли использовать массивы C-стиля, - это передать std::array такой функции, как это:

void f(std::array<int, 100> const & array);

Если вы не пройдете по ссылке, данные будут скопированы. Это следует за поведением большинства хорошо продуманных типов, но отличается от массивов C-стиля при передаче функции (это больше похоже на поведение массива C-стиля внутри структуры).