Этот вопрос был вызван аналогичным вопросом: Как delete [] «знает» размер массива операндов?
Мой вопрос немного отличается: Есть ли способ программно определить размер массива C++? А если нет, то почему? Каждая функция, которую я видел, которая принимает массив, также требует целочисленного параметра для задания размера. Но, как указано в связанном вопросе, delete[] должен знать размер памяти, которую нужно освободить.
Рассмотрим этот код C++:
int* arr = new int[256];
printf("Size of arr: %d\n", sizeof(arr));
Это напечатает «Size of arr: 4», который равен размеру указателя. Было бы неплохо иметь функцию, которая печатает 256, но я не думаю, что она существует в C++. (Опять же, отчасти вопрос в том, почему его не существует.)
Разъяснение: Я знаю, что если бы я объявил массив в стеке вместо кучи (т.е. «int arr[256];»), то оператор sizeof вернет 1024 (длина массива * sizeof (int)).
спасибо, я пропустил это, потому что на самом деле я использовал char [] в своем тестовом коде (и sizeof (char) == 1)
Хотя это только гипотетически - поскольку это не работает - я должен указать, что вам следовало написать printf("Size of arr: %d\n", sizeof(*arr)); вместо printf("Size of arr: %d\n", sizeof(*arr));, поскольку вы хотите получить размер разыменованного указателя.





Обычный способ справиться с этим - использовать вектор
int main()
{
std::vector<int> v(256);
printf("size of v is %i capacity is %i\n", sizeof(int) * v.size(), sizeof(int) * v.capacity());
}
или заранее определите размер
const int arrSize = 256;
int main()
{
int array[arrSize];
printf("Size of array is %i", sizeof(int) * arrSize);
}
sizeof(int) * arrSize такой же, как malloc ('sizeof (int) * arrSize'), не так ли ??
Это потому, что ваша переменная arr - всего лишь указатель. Он хранит адрес определенного места в памяти, ничего не зная об этом. Вы объявляете его как int *, что дает компилятору некоторое представление о том, что делать, когда вы увеличиваете указатель. Кроме того, вы могли указывать на начало или конец массива, либо в стек, либо в недопустимую память. Но я согласен с вами, отсутствие возможности вызвать sizeof очень раздражает :)
QuantumPete
Но система каким-то образом знает размер массива, иначе "delete [] arr" не сработает.
Что ж, система знает во время выполнения, но sizeof - это вызов времени компиляции.
delete [] arr будет знать размер массива, но не будет ли массив размещен в стеке.
Нет, нет никакого способа сделать это, вы должны следить за тем, насколько он велик внешне. Такие классы, как std::vector, делают это за вас.
Нет, в Standard C++ это невозможно.
Нет действительно веской причины, о которой я знаю. Вероятно, размер считался деталью реализации и лучше не раскрывать. Обратите внимание: когда вы говорите malloc (1000), нет гарантии, что возвращаемый блок составляет 1000 байт - только то, что это по меньшей мере 1000 байт. Скорее всего, это около 1020 (1 КБ минус 4 байта на накладные расходы). В этом случае размер «1020» является важным для запоминания библиотеки времени выполнения. И, конечно, это будет меняться между реализациями.
Вот почему комитет по стандартам добавил std: vector <>, который отслеживает его точный размер.
Следует отметить, что new [] также сохраняет количество запрошенных элементов, чтобы вызвать правильное количество конструкторов и деструкторов для массива. Где это хранится, снова зависит от реализации. Причина, по которой я не могу указать способ получения, находится вне моего понимания.
Я думаю, что "веская причина" в том, что массивы вообще не являются объектами. Массив - это просто необработанный блок памяти. Размер - это данные управления памятью, а не объектные данные. Вы могли бы написать класс Array, который отслеживал бы память и размер, но вы могли бы просто использовать std :: vector и не беспокоиться об этом.
Ага ... Конечно. Тип int * не может знать, был ли массив, на который он указывал, новым массивом, локальным массивом или каким-то местом в середине массива.
@Herms: std :: string [10] определенно не является необработанной памятью, но это массив.
workmad3, возможно, только для элементов с нетривиальным деструктором и для типов с определяемым пользователем оператором delete, который хочет, чтобы размер был известен. для чего-либо другого достаточно не хранить номер
Было бы очень удобно иметь функцию, которая возвращала бы размер выделения особенно, если бы он был больше запрошенного.
delete [] знает размер, который был выделен. Однако эти знания находятся во время выполнения или в диспетчере памяти операционной системы, что означает, что они недоступны для компилятора во время компиляции. И sizeof() не является реальной функцией, он фактически вычисляется компилятором как константа, чего он не может сделать для динамически выделяемых массивов, размер которых неизвестен во время компиляции.
Также рассмотрите этот пример:
int *arr = new int[256];
int *p = &arr[100];
printf("Size: %d\n", sizeof(p));
Как компилятор узнает размер p? Корень проблемы в том, что массивы в C и C++ не являются объектами первого класса. Они распадаются на указатели, и компилятор или сама программа не могут узнать, указывает ли указатель на начало блока памяти, выделенного new, или на отдельный объект, или на какое-то место в середине кусок памяти, выделенный new.
Одна из причин этого заключается в том, что C и C++ оставляют управление памятью на усмотрение программиста и операционной системы, поэтому в них также отсутствует сборка мусора. Реализация new и delete не является частью стандарта C++, поскольку C++ предназначен для использования на различных платформах, которые могут управлять своей памятью по-разному. Можно позволить C++ отслеживать все выделенные массивы и их размеры, если вы пишете текстовый процессор для окна Windows, работающего на новейшем процессоре Intel, но это может быть совершенно невозможно, когда вы пишете встроенную систему, работающую на DSP.
В C++ есть абсолютно массивы. А как еще вы объяснили бы, почему с этим "char x [4]; size_t sz = sizeof (x);" что 'sz' будет присвоено 4?
Кевин, x на самом деле не массив. Это указатель на кусок памяти, который находится в стеке. А поскольку он был размещен в стеке статически, компилятор знает его размер. Если вы передадите x функции, она не узнает своего размера. Массивы в C или C++ не являются объектами первого класса.
В качестве примечания: sizeof - это оператор, а не функция, как указано на плакате, что это не настоящая функция.
Дима, есть совсем массивы. массивы отличаются от указателей. к сожалению, многие учителя путают это и говорят своим ученикам, что они «всего лишь» указатели. нет, это не так. как еще объяснить это: char const ** s = & "bar"; не компилируется? [...]
(если вы утверждаете, что «bar» - это указатель на символьный массив «bar», который является «просто» указателем?). и тот факт, что массивы не являются объектами первого класса, не означает, что «настоящих массивов нет». функции тоже не первоклассные. так разве у вас нет «настоящих функций» в C++?
litb, причина char const ** s = & "bar"; не компилируется, так как "bar" является константой, а не lvalue, поэтому вы не можете взять его адрес. Это то же самое, что int * p = & 5; который тоже не компилируется.
Говоря об отсутствии «настоящих» массивов, я имел в виду именно то, что массивы C или C++ не являются объектами первого класса. Вы, конечно, используете массивы в C или C++, но функция void foo (int * a), принимающая указатель в качестве аргумента, не знает, является ли a массивом или указателем на одно целое число.
@Dima, тогда отредактируйте ответ, чтобы это было сказано. Я могу написать функции, которые принимают массивы, но не указатели, если вам нужны дополнительные доказательства.
кроме того, программа действительно знает размер выделенного массива if, иначе она не смогла бы удалить правильный номер до освобождения.
@MooingDuck, я считаю, что мой ответ уже достаточно ясен.
Понятно, но почти все не так. Уже существует ситуация, когда sizeof - это время выполнения, а не время компиляции, существуют массивы делать, и есть способы реализации, чтобы узнать размер всех массивов. Даже DSP должен сохраняет информацию о размере для распределений.
void foo(int *a); принимает указатель, void foo(int (&a)[5]); принимает массив. Имена массивов превращаются в указатели, что отстой, но это не значит, что массивы и указатели - одно и то же.
@ Дима: Ты ошибаешься. Массивы существуют, и "x"является имеет lvalue. Распад массива и указателя очень плох, но они являются разные вещи. Функция void foo(int* a) принимает указатель на одно целое число. Тот факт, что этот указатель может указывать на элемент массива, будь он первым по убыванию указателя или любым другим способом, не имеет значения.
Любой, кто это видит, должен проголосовать против. Тонны и тонны ошибок здесь. Это распространенное заблуждение
Хорошо всем. Первоначальный вопрос заключался в том, почему sizeof (arr) не возвращает размер массива. Думаю, я ответил на это.
Еще я хочу сказать, что массивы C не являются объектами первого класса. Их размер должен быть указан во время компиляции, они должны быть размещены в стеке, и они распадаются на указатели. В большинстве практических целей, если вы хотите передать массив функции, вы должны передать указатель на первый элемент и размер массива. Даже если вы напишете библиотеку функций, работающих с векторами или матрицами фиксированного размера, вы все равно будете передавать указатели и размеры, потому что вы не можете ожидать, что вызывающий объект выделит все в стеке. См. Пример в «Числовых квитанциях».
@Dima: Когда вы говорите «объект 1-го класса», что именно вы имеете в виду? Когда вы объявляете int a[4];, получается непрерывный 4-байтовый блок памяти. Что в этом отсутствует, что говорит о том, что это не массив? Я не пытаюсь наваливать, я пытаюсь получить разъяснения.
@JohnDibling, объект первого класса - это сущность, которая может быть создана во время выполнения, передана как параметр, возвращена из подпрограммы или назначена переменной. en.wikipedia.org/wiki/First-class_object Из той же статьи: Во многих старых языках (например, C) массивы не были первоклассными: их нельзя было назначить как объекты или передать как параметр подпрограмме; только их элементами можно было напрямую манипулировать.
@Dima: Значит, вы не утверждаете, что в C++ нет массивов, просто они не являются «объектами 1-го класса» по вашему определению?
@JohnDibling, я сказал в ответ, что в C нет «настоящих» массивов, и позже я пояснил в комментариях, что имел в виду, что массивы C не являются объектами 1-го класса. И не по моему определению, а по общепринятому определению.
@JohnDibling, например, сравнивает массивы C с массивами Java. Массивы Java могут быть назначены друг другу, они могут быть переданы в функции, и вам не нужно постоянно передавать вместе с ними их размер.
@ Дима: Хорошо. Вы могли бы просто сказать «Да».
@JohnDibling, и поскольку вы упомянули C++, то std :: vector фактически является «настоящим» массивом.
@Dima "bar" - это lvalue, а &"bar" - действительный. Однако тип последнего - char (*)[4], который несовместим с char **.
@Dima, я не думаю, что вы ответили на вопрос: «Если delete [] может получить доступ к размеру выделенного массива, почему не может программист». Извиняюсь, если я упустил суть.
Краткий ответ: потому что так определяются новые / удаляемые. Длинный ответ: new / delete - это внутренняя бухгалтерия, скрытая от внешнего мира. То, как они это делают, намеренно не стандартизировано и зависит от компилятора. Одна из причин сокрытия этой информации заключается в том, что new () обычно выделяет больше памяти, чем вы запрашиваете, из-за выравнивания и из-за того, что он может использовать часть выделенной памяти для своей собственной бухгалтерии. Поскольку этот учет зависит от компилятора, ваш код не должен на него полагаться.
По сути, вы не можете:
void foo(int* arr);
int arr[100] = {0};
foo(arr+1); // Calls foo with a pointer to 100-1 elements.
Массив C++ - это не что иное, как набор объектов, которые хранятся в непрерывной области памяти. Поскольку между ними нет дыр (заполнение - это объекты внутри), вы можете найти следующий элемент массива, просто вставив указатель. На уровне ЦП это простая настройка. C++ вставляет только множитель sizeof (element).
Обратите внимание, что реализации могут выбрать реализацию «жирных указателей», которые содержат границы массива. Они должны быть вдвое больше, так как вам нужно будет ссылаться на какой-то «дескриптор, связанный с массивом». В качестве побочного эффекта в таких реализациях вы можете вызвать delete [] (1+new int[5]);
К сожалению, это невозможно. В C и C++ обязанность программиста - помнить о длине массива, поскольку длина массива нигде не хранится. Delete [] и free () запоминают размер выделенного блока, но они могут выделить больше памяти, чем запрошено, поэтому их внутренние структуры данных, хранящие размеры выделенных блоков памяти, могут не дать вам точного размера вашего массива.
Обратите внимание, что векторы C++ STL, которые в основном представляют собой массивы, обернутые в класс с некоторыми вспомогательными функциями, действительно хранят длину массива, поэтому, если вам действительно нужна эта функция, вы можете просто использовать векторы.
В C++ нет переносимого способа определения размера динамически распределяемого массива по только его указателю. C++ сделан очень гибким и дает пользователю возможности. Например, стандарт не определяет, как должны работать распределители памяти, например добавив заголовок необходимого размера. Отсутствие необходимости в заголовке обеспечивает большую гибкость.
В качестве одного из примеров рассмотрим строку, реализованную как массив char *. Обычно для выделения подстрок используются указатели в середине массива. В качестве примера см. Функцию strtok в стандартной библиотеке C. Если бы какой-то заголовок нужно было встраивать непосредственно перед каждым массивом, вам нужно было бы удалить части массива перед подстрокой.
Альтернативный способ обработки заголовков - иметь заголовки массива в одном блоке памяти и указывать на необработанную память массива в другом месте. Во многих ситуациях для каждой ссылки потребуется два поиска указателя, что сильно снизит производительность. Есть способы преодоления этих недостатков, но они добавляют сложности и снижают гибкость реализации.
Шаблон std :: vector - мой любимый способ сохранить размер массива, привязанного к самому массиву.
C - переносимый язык ассемблера с улучшенным синтаксисом.
strtok будет работать точно так же, если массивы имеют заголовки, потому что strtok принимает указатели на символы, а не на массивы.
В общем нет. Массивы в C и C++ - это просто блоки памяти без прикрепленной бухгалтерской информации. В общем случае это невозможно без сохранения длины массива в памяти и дополнительных накладных расходов.
Исключение составляют статические массивы. Например, если вы объявите: int a[50], то sizeof(a) будет работать. Это возможно, потому что [50] является частью статического типа массива: он известен компилятору. sizeof интерпретируется во время компиляции.
Однако, если вы создадите указатель: int *p = a, тогда sizeof(p) вернет размер указателя, как вы упомянули, а не размер массива, потому что компилятор не знает, на что указывает p.
C++ решил добавить new, чтобы сделать типизированный malloc, тогда new должен знать оба размера e номеров элементов для вызова ctors, поэтому delete для вызова dtors. В первые дни вы должны фактически передать, чтобы удалить числа, которые вы передали объектам new.
string* p = new string[5];
delete[5] p;
Однако они думали, что если использовать new <type> [], накладные расходы на число будут небольшими. Поэтому они решили, что new [n] должен запомнить n и передать его на удаление. Есть три основных способа его реализовать.
Возможно, удастся получить такой размер:
size_t* p = new size_t[10];
cout << p[-1] << endl;
// Or
cout << p[11] << endl;
Или, черт возьми, ничего из этого.
На самом деле есть способ определить размер, но он небезопасен и будет отличаться от компилятора к компилятору .... поэтому его вообще не следует использовать.
Когда вы это сделаете: int * arr = новый int [256];
256 не имеет значения, вам будет предоставлено 256 * sizeof (int), предполагая, что для этого случая 1024, это значение будет сохранено, вероятно, в (arr - 4)
Итак, чтобы дать вам количество "предметов"
int * p_iToSize = arr - 4;
printf ("Количество элементов% d", * p_iToSize / sizeof (int));
Для каждого malloc, new, независимо от того, что предшествует полученному вами блоку памяти Continos, также выделяется пространство, зарезервированное с некоторой информацией о блоке памяти, который вам был предоставлен.
Тем не менее, это действительно ответ на вопрос.
интересно, :) в качестве дополнительных 2 цента вы можете перегрузить "новый" и реализовать управление памятью, как вам нравится, вы можете иметь его, как описывает Жоао, или хранить каждый указатель на карте с его соответствующим размером ... короче говоря, есть много безумных способов, но я бы не стал их использовать: p
как насчет массива символов? char * arr = новый символ [100];
Is there any way to determine the size of a C++ array programmatically? And if not, why?
@ Дима,
How would the compiler know what the size of p is?
Компилятор должен знать размер p; в противном случае он не сможет реализовать delete[]. Компилятору не нужно никому сообщать, как он это выясняет.
Чтобы убедиться в этом, сравните указатель, возвращаемый operator new[], с указателем, возвращаемым new[].
Компилятор не может этого знать
char *ar = new char[100]
представляет собой массив из 100 символов, потому что он не создает фактический массив в памяти, он просто создает указатель на 100 неинициализированных байтов в памяти.
Если вы хотите узнать размер данного массива, просто используйте std :: vector. std :: vector - это просто лучший массив.
Когда вы создаете указатели на массивы (создаете оболочку с шаблоном для указателей), вы не можете, но когда вы создаете массив объектов, Вы можете получить размер массива так:
char* chars=new char[100];
printf("%d",*((int*)chars-1));
Функция delete[] должна деконструировать все объекты в ней. для этого ключевое слово new[] помещает количество элементов за всем массивом.
Тело массива выглядит так:
int count;
ObjectType* data; //This value is returned when using new[]
Вы можете просто создать дополнительный элемент массива, а затем применить наиболее маловероятное число, которое будет храниться в массиве. Затем вы можете определить количество элементов с помощью некоторой функции, передав это число.
В случае объявления и инициализации массива в момент создания вы можете затем просканировать его, а затем сгенерировать число, которое не соответствует ни одному из элементов массива. Но если вы затем измените один из элементов, вы не узнаете, хранит ли этот элемент то же значение, что и последний элемент, поэтому вам придется сгенерировать новое число для сохранения в последнем элементе. Пройдя через все это, вы с таким же успехом можно просто сохранить в переменной общее количество элементов на момент создания. И это, вероятно, будет иметь место, если вы используете массив только внутри функции.
Это нечетко и непрактично, но при этом мешает измерению. несерьезный ответ.
Введение волшебной строки («наиболее невероятного числа») в данные является анти-шаблоном по определенной причине. Что произойдет, если это маловероятное число действительно будет получено по непредвиденным программистом причинам?
В зависимости от вашего приложения вы можете создать «контрольное значение» в конце вашего массива.
У контрольного значения должно быть какое-то уникальное свойство.
Затем вы можете обработать массив (или выполнить линейный поиск) для контрольного значения, считая по мере продвижения. Как только вы достигнете значения дозорного, у вас будет счетчик массива.
Для простой строки C завершающий символ \ 0 является примером контрольного значения.
Немного волшебства:
template <typename T, size_t S>
inline
size_t array_size(const T (&v)[S])
{
return S;
}
И вот как мы это делаем в C++ 11:
template<typename T, size_t S>
constexpr
auto array_size(const T (&)[S]) -> size_t
{
return S;
}
Очень полезное и красивое решение. Только одно: вместо этого я бы использовал size_t в качестве второго параметра шаблона.
Теперь есть std :: array, эффективная оболочка времени компиляции вокруг массива постоянного размера:
#include <array>
int main (int argc, char** argv)
{
std::array<int, 256> arr;
printf("Size of arr: %ld\n", arr.size());
}
Параметры <type, #elements>.
Вы также получаете несколько других тонкостей, таких как итераторы, empty () и max_size ().
я делаю это путем деления размера массива на размер первого элемента
int intarray[100];
printf ("Size of the array %d\n", (sizeof(intarray) / sizeof(intarray[0]));
Он печатает 100
Пожалуйста, перечитайте вопрос (последняя строка), автору это хорошо известно. это не то, о чем спрашивают.
Фактически, если вы разместили массив в стеке, оператор sizeof вернет 1024, что составляет 256 (количество элементов) * 4 (размер отдельного элемента). (sizeof (arr) / sizeof (arr [0])) даст результат 256.