Подтвердить что ты не робот

С++ не сообщает вам размер динамического массива. Но почему?

Я знаю, что на С++ нет способа получить размер динамически создаваемого массива, например:

int* a;
a = new int[n];

Что я хотел бы знать: почему? Неужели люди просто забывают об этом в спецификации С++ или есть техническая причина для этого?

Разве информация не хранится где-то? В конце концов, команда

delete[] a;

похоже, знает, сколько памяти он должен выпустить, поэтому мне кажется, что delete[] имеет некоторый способ узнать размер a.

4b9b3361

Ответ 1

Вы часто обнаружите, что диспетчеры памяти будут выделять пространство в определенном количестве, например, на 64 байта.

Итак, вы можете запросить новый int [4], т.е. 16 байт, но менеджер памяти выделит 64 байта для вашего запроса. Чтобы освободить эту память, вам не нужно знать, сколько памяти вы просили, только чтобы он выделил вам один блок из 64 байтов.

Следующий вопрос может быть, может ли он не хранить запрошенный размер? Это дополнительные накладные расходы, которые не все готовы платить. Например, Arduino Uno имеет только 2 КБ оперативной памяти, и в этом контексте 4 байта для каждого выделения внезапно становятся значительными.

Если вам нужна эта функциональность, у вас есть std::vector (или эквивалент), или у вас языки более высокого уровня. C/С++ был разработан таким образом, чтобы вы могли работать с такими небольшими накладными расходами, как вы решили использовать, что является одним из примеров.

Ответ 2

Следующее из основного правила "не плати за то, что вам не нужно". В вашем примере delete[] a; не нужно знать размер массива, потому что int не имеет деструктора. Если вы написали:

std::string* a;
a = new std::string[n];
...
delete [] a;

Затем delete должен вызывать деструкторы (и должен знать, сколько нужно позвонить) - в этом случае new должен сохранить этот счет. Однако, учитывая, что его не нужно спасать во всех случаях, Бьярн решил не предоставлять ему доступ.

(Оглядываясь назад, я думаю, что это была ошибка...)

Даже с int, конечно, что-то должно знать о размере выделенной памяти, но:

  • Многие распределители округляют размер до некоторого удобного множества (например, 64 байта) для удобства выравнивания и удобства. Распределитель знает, что блок имеет длину 64 байта, но он не знает, является ли это потому, что n был 1... или 16.

  • Библиотека времени выполнения С++ может не иметь доступ к размеру выделенного блока. Если, например, new и delete используют malloc и free под капотом, то библиотека С++ не имеет возможности узнать размер блока, возвращаемого malloc. (Обычно, конечно, new и malloc являются частью одной и той же библиотеки, но не всегда.)

Ответ 3

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

Рассмотрим фиктивную функцию, которая возвращает количество элементов, на которые указывает указатель.
Позвольте называть его "размер".

Звучит неплохо, правда?

Если бы не тот факт, что все указатели созданы равными:

char* p = new char[10];
size_t ps = size(p+1);  // What?

char a[10] = {0};
size_t as = size(a);     // Hmm...
size_t bs = size(a + 1); // Wut?

char i = 0;
size_t is = size(&i);  // OK?

Можно утверждать, что первым должен быть 9, второй 10, третий 9 и последний 1, но для этого вам нужно добавить "тег размера" на каждый объект.
A char потребует 128 бит памяти (из-за выравнивания) на 64-битной машине. Это в шестнадцать раз больше, чем нужно.
(Выше, десятисимвольный массив a потребует не менее 168 байт.)

Это может быть удобно, но это также неприемлемо дорого.

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

Ответ 4

Вы правы, что какая-то часть системы должна знать что-то о размере. Но получение этой информации, вероятно, не покрывается API системы управления памятью (думаю malloc/free), и точный размер, который вы запросили, может не быть известен, поскольку он, возможно, был округлен.

Ответ 5

Есть любопытный случай перегрузки operator delete, который я найден в виде:

void operator delete[](void *p, size_t size);

Параметр size по умолчанию имеет размер (в байтах) блока памяти, на который указывает void * p. Если это так, разумно, по крайней мере, надеяться, что оно имеет значение, переданное вызовом operator new, и поэтому просто нужно будет делить на sizeof (type), чтобы доставить количество элементов, хранящихся в массиве.

Что касается "почему" части вашего вопроса, то правило Martin "не платить за то, что вам не нужно" кажется наиболее логичным.

Ответ 6

Невозможно узнать, как вы собираетесь использовать этот массив. Размер выделения не обязательно соответствует номеру элемента, поэтому вы не можете просто использовать размер распределения (даже если он доступен).

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

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

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

Другим примером является FFT, который рекурсивно манипулирует данными, начиная с 2x2 "бабочек" и возвращается обратно ко всему массиву.

Чтобы исправить управляемый массив, вам теперь нужно "что-то еще" для исправления этого дефекта и что "что-то другое" называется "итераторами". (Теперь вы управляете массивами, но почти никогда не передаете их никаким функциям, потому что вам нужны итераторы + 90% времени.)

Ответ 7

Размер массива, выделенного с помощью new[], явно не сохраняется в любом месте, поэтому вы не можете получить к нему доступ. А оператор new[] не возвращает массив, а только указатель на первый элемент массива. Если вы хотите узнать размер динамического массива, вы должны сохранить его вручную или использовать классы из библиотек, таких как std::vector