Вычисление размера массива
Я использую следующий макрос для вычисления размера массива:
#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-стиля внутри структуры).