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

Почему мы можем удалить массивы, но не знать длину в C/С++?

Возможный дубликат:
C-программирование: как бесплатно узнать, сколько бесплатно?

Как мы можем удалить динамически распределенные массивы, но мы не можем узнать, сколько у них элементов? Разве мы не можем просто разделить размер ячейки памяти на размер каждого объекта?

4b9b3361

Ответ 1

В С++ оба...

  • размер (байты), запрошенный новым, новым [] или вызовом malloc, и
  • количество элементов массива, запрошенных в новом динамическом размещении []

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

Иногда компилятор может видеть там распределение по постоянному размеру и надежно связывать его с соответствующим освобождением, поэтому он может генерировать код, настроенный для этих значений, известных во время компиляции (например, встраивание и циклическое разворачивание), но в (и при обращении с внешними входами) компилятору может потребоваться хранить и извлекать # элементов во время выполнения: достаточно места для счетчика #element, например, - непосредственно перед или после адреса, возвращаемого для содержимого массива, с delete [], зная об этом соглашении. На практике компилятор может решить всегда обрабатывать это во время выполнения только для простоты, которая приходит с согласованностью. Существуют другие возможности времени выполнения: например. # элементы могут быть получены из некоторого понимания конкретного пула памяти, из которого было выполнено распределение в сочетании с размером объекта.

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

(Размер ячейки памяти может быть больше точного размера, необходимого для запрашиваемого количества элементов - этот размер запоминается библиотекой распределения памяти, которая может быть библиотекой черного ящика, не зависящей от компилятора С++).

Ответ 2

Распределитель памяти запоминает размер выделения, но не дает его пользователю. Это верно в C с malloc и в С++ с new.

"Размер ячейки памяти" не может быть получен. Если вы делаете

int *a = new int[N];
std::cout << sizeof(a);

вы обнаружите, что он печатает sizeof(int *), который является постоянным (для данной платформы).

Ответ 3

Общим путем в С++ является использование std::vector вместо массива.

std::vector имеет метод size, который возвращает количество элементов.

Если возможно, вам лучше использовать std::vector вместо массива, где это возможно.

Ответ 4

Причина в том, что языки C не раскрывают эту информацию, хотя может быть доступной для конкретной реализации. (Действительно, для массива new [] в С++ размер должен отслеживаться для вызова деструкторов для каждого объекта, но как это делается, зависит от конкретного компилятора.)

Причина этого неразглашения заключается в том, что разработчики компиляторов и разработчики платформ имеют больше свободы в том, как они реализуют распределения памяти с переменным размером. Также нет необходимости знать эту информацию в целом, поэтому не имеет смысла требовать, чтобы каждая платформа C предоставляла эту информацию.

Кроме того, одна практическая причина (для malloc и др.) заключается в том, что они не дают вам то, о чем вы просили: если вы спросите malloc для 30 байт памяти, это, скорее всего, даст вам 32 байта (или некоторые другая более высокая гранулярность распределения). Таким образом, единственной доступной информацией является 32 байта, и вы, как программист, не очень хорошо используете эту информацию.

Ответ 5

Две вещи работают против него

  • во-первых, массивы и указатели взаимозаменяемы - массив не имеет никакого дополнительного понимания его длины. (* Все комментаторы умных задниц пытались прокомментировать фундаментальные различия между массивами и указателями, следует отметить, что ничто из этого не имеет никакого отношения к этому вопросу;) *)

  • во-вторых, потому что знание размера выделения - это дело кучи, а куча не предоставляет какого-либо стандартного способа обнаружения размера выделения.

Symbian, однако, имеет функцию AllocSize(), из которой вы можете узнать, сколько элементов находится в массиве. Тем не менее, иногда распределения больше, чем требуется, поскольку он управляет памятью в выровненных по слову фрагментах.

Ответ 6

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

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

например, itanium 64 abi хранит куки файл (количество элементов) в ведущих байтах распределения (в частности, не-POD), а затем при необходимости накладывается на естественное выравнивание объектов. вы затем возвращаете (от new[]) адрес первого используемого элемента, а не адрес фактического распределения. поэтому есть куча непереносимости бухгалтерии.

класс-оболочка - это простой способ справиться с этим.

это действительно интересное упражнение для написания распределителей, переопределение объектов:: new/delete, операторов размещения и просмотр того, как все это сочетается (хотя это не особенно тривиальное упражнение, если вы хотите, чтобы распределитель использовался в производственном коде).

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

кроме того, нет гарантии, что распределитель выделил точно количество запрошенных байтов (так что ваши счета могут быть неправильными, если вы определяете счет на основе размера выделения). если вы проходите через интерфейсы malloc, вы можете найти свое распределение... но это все еще не очень полезно, переносимо или безопасно для любого нетривиального случая.

Update:

@Default существует множество причин для создания собственного интерфейса. как отметил Тони, std::vector - это одна известная реализация. основа для такой обертки проста (интерфейс заимствован из std::vector:

/* holds an array of @a TValue objects which are created at construction and destroyed at destruction. interface borrows bits from std::vector */
template<typename TValue>
class t_array {
    t_array(const t_array&); // prohibited
    t_array operator=(const t_array&); // prohibited
    typedef t_array<TValue>This;
public:
    typedef TValue value_type;
    typedef value_type* pointer;
    typedef const value_type* const_pointer;
    typedef value_type* const pointer_const;
    typedef const value_type* const const_pointer_const;
    typedef value_type& reference;
    typedef const value_type& const_reference;

    /** creates @a count objects, using the default ctor */
    t_array(const size_t& count) : d_objects(new value_type[count]), d_count(count) {
        assert(this->d_objects);
        assert(this->d_count);
    }

    /** this owns @a objects */
    t_array(pointer_const objects, const size_t& count) : d_objects(objects), d_count(count) {
        assert(this->d_objects);
        assert(this->d_count);
    }

    ~ t_array() {
        delete[] this->d_objects;
    }

    const size_t& size() const {
        return this->d_count;
    }

    bool empty() const {
        return 0 == this->size();
    }

    /* element access */
    reference at(const size_t& idx) {
        assert(idx < this->size());
        return this->d_objects[idx];
    }

    const_reference at(const size_t& idx) const {
        assert(idx < this->size());
        return this->d_objects[idx];
    }

    reference operator[](const size_t& idx) {
        assert(idx < this->size());
        return this->d_objects[idx];
    }

    const_reference operator[](const size_t& idx) const {
        assert(idx < this->size());
        return this->d_objects[idx];
    }

    pointer data() {
        return this->d_objects;
    }

    const_pointer data() const {
        return this->d_objects;
    }

private:
    pointer_const d_objects;
    const size_t d_count;
};

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

  • сделать объект с меньшим интерфейсом. минимализм хорош.
  • сделать объект, который не требует распределителя. например: t_array приведет к уменьшению количества экспортированных символов, а также более коротких имен для этих символов (путем удаления аргумента распределителя).
  • чтобы сделать варианты, которые обрабатывают дополнительные константные случаи. в приведенном выше примере часто нет причин менять то, на что указывает контейнер. поэтому в приведенном выше t_array используются 2 константных члена, каждый из которых обеспечивает меньшее отклонение, чем std::vector. хороший оптимизатор должен использовать эти детали. это также мешает пользователям совершать случайные ошибки.
  • чтобы сократить время сборки. если ваши потребности просты, как t_array, или даже более простые, вы можете сократить время сборки, используя минимальный интерфейс.

другие случаи:

  • для создания объекта с большим интерфейсом или более функций
  • сделать объект с дополнительными средствами отладки
  • сделать объект, который может быть подклассом (большинство реализаций std::vector не предназначены для подкласса)
  • сделать объект, который является потокобезопасным

Ответ 7

Все это прекрасно в философии "сохранить это просто" C: вы ДОЛЖНЫ, в какой-то момент, решить, какой размер массив/буфер/что-то нужное; так что держите это значение и это. зачем тратить вызов функции для восстановления информации, которую у вас уже есть?