Определить размер массива C ++ программно? - PullRequest
60 голосов
/ 13 октября 2008

Этот вопрос был вдохновлен похожим вопросом: Как 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) )).

Ответы [ 20 ]

65 голосов
/ 13 октября 2008

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.

18 голосов
/ 13 октября 2008

Нет, в Standard C ++ этого не существует.

Нет действительно веской причины, по которой я не знаю об этом. Вероятно, размер был рассмотрен как деталь реализации, и лучше всего не раскрываться. Обратите внимание, что когда вы говорите malloc (1000), нет гарантии, что возвращаемый блок будет 1000 байтов - только то, что это как минимум 1000 байтов. Скорее всего, это около 1020 (1K минус 4 байта для служебных данных). В этом случае размер «1020» является важным для запоминающейся библиотеки времени выполнения. И, конечно, это будет меняться между реализациями.

Именно поэтому комитет по стандартам добавил стандарт std: vector <>, который точно отслеживает его размер.

16 голосов
/ 13 октября 2008

Ну, на самом деле есть способ определить размер, но он не "безопасен" и будет отличаться от компилятора к компилятору .... , поэтому его вообще не следует использовать .

Когда вы делаете: int * arr = new int [256];

256 не имеет значения, вам дадут 256 * sizeof (int), если для этого случая 1024, это значение будет сохранено, вероятно, в (arr - 4)

Итак, чтобы дать вам количество «предметов»

int * p_iToSize = arr - 4;

printf («Количество элементов% d», * p_iToSize / sizeof (int));

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

5 голосов
/ 13 октября 2008

Обычный способ справиться с этим - использовать вектор

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);
}
3 голосов
/ 10 июня 2013

Немного магии:

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; 
}
3 голосов
/ 13 октября 2008

C ++ решил добавить new, чтобы сделать malloc безопасным, чем new должен знать оба размера e числа элементов для вызова ctors, поэтому delete для вызова dtors В первые дни вы должны фактически передать, чтобы удалить номера, которые вы передали новым объектам.

string* p = new string[5];
delete[5] p;

Однако они подумали, что если использовать новый [], то издержки на число будут небольшими. Поэтому они решили, что new [n] должен запомнить n и передать его для удаления. Существует три основных способа его реализации.

  1. сохранить хэш-таблицу указателей на размер
  2. написал это прямо возле вектора
  3. сделать что-то совершенно другое

Возможно, можно получить такой размер:

size_t* p = new size_t[10];
cout << p[-1] << endl;
// Or
cout << p[11] << endl;

Или, черт возьми, ничего из этого.

3 голосов
/ 15 мая 2013

В зависимости от вашего приложения, вы можете создать «значение часового» в конце вашего массива.

Значение часового должно иметь уникальное свойство.

Затем вы можете либо обработать массив (или выполнить линейный поиск) для значения часового, считая на ходу. Как только вы достигнете значения часового, у вас будет счетчик массива.

Для простой строки C конец \ 0 является примером значения часового.

2 голосов
/ 13 октября 2008

Это потому, что ваша переменная arr является только указателем. Он содержит адрес определенного места в памяти, ничего не зная об этом. Вы объявляете его как int *, что дает компилятору некоторое указание на то, что делать, когда вы увеличиваете указатель. Кроме этого, вы можете указывать в начало или конец массива или в стек или в недопустимую память. Но я согласен с вами, неумение называть sizeof очень раздражает :)

QuantumPete

2 голосов
/ 13 октября 2008

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

В качестве одного примера рассмотрим строку, реализованную в виде массива char *. Обычно для выделения подстрок используются указатели на середину массива. В качестве примера см. Функцию strtok в стандартной библиотеке C. Если какой-то заголовок необходимо было вставить непосредственно перед каждым массивом, вам нужно было бы удалить части массива перед подстрокой.

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

Шаблон std :: vector - мой любимый способ привязать размер массива к самому массиву.

C - это переносимый ассемблер с лучшим синтаксисом.

1 голос
/ 07 декабря 2013

Теперь есть 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 ().

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...