В C ++ информация о длине динамического массива связана с указателем или адресом первого элемента?

Например:

int *a, *b;
a = new int[10];
b = new int(12);
b = a; // I know there's memory leak, but let's ignore it first
delete [] b; // line L

Что случится? Будет ли удаляться весь массив?

Что если строка L будет заменена на эту:
б = а + 1;
удалить [] b;

Или этим:
A ++;
удалить [] a;

И, наконец, если длина динамического массива связана с начальным адресом или, другими словами, связана с самим массивом, есть ли у нас какой-либо способ получить его длину, не используя другую переменную для хранения длины?

Большое спасибо!

4

Решение

Информация о размере блока памяти и длине массива связана с адресом объекта, который является адресом первого элемента массива.

То есть ваш delete[] безопасно в данном коде

int *a, *b;
a = new int[10];
b = new int(12);
b = a; // I know there's memory leak, but let's ignore it first
delete [] b; // line L

Тем не менее, нет портативного способа 1доступ связанная информация. Это деталь реализации. Использовать std::vector если вам нужна такая информация.


Чтобы понять, почему информация не доступна, сначала обратите внимание, что размер блока памяти, необходимый для освобождения, может быть больше длины массива, умноженной на размер элемента массива. Итак, мы имеем дело с двумя отдельными значениями. А для массива типа элемента POD, где вызовы деструктора элемента не нужны, не должно быть явно сохраненного значения длины массива.

Даже для массива, где необходимы вызовы деструктора, необязательно должно быть явно сохраненное значение длины массива, связанное с массивом. Например, в коде шаблона p = new int[n]; whatever(); delete[] p;в принципе, компилятор может поместить длину массива в какое-то несвязанное место, где он может быть легко доступен с помощью кода, сгенерированного для delete выражение. Например. это может быть занесено в регистр процессора.

В зависимости от того, насколько умным является компилятор, необязательно также должен быть явно сохраненный размер блока памяти. Например, компилятор может заметить, что в какой-то функции fтри массива a, b а также c выделяются, их размеры известны при первом выделении, а все три освобождаются в конце. Таким образом, компилятор может заменить три выделения одним, а также заменить три освобождения одним (эта оптимизация явно разрешена 2C ++ 14 §5.3.4 / 10).



1 За исключением того, что в C ++ 14 и более поздних версиях есть функция освобождения, которая немного запаздывает.
2 C ++ 14 §5.3.4 / 10: «Реализациям разрешено пропускать вызов заменяемой функции глобального распределения (18.6.1.1, 18.6.1.2).
Когда это происходит, вместо этого хранилище обеспечивается реализацией или обеспечивается расширением
выделение другого новое выражение. Реализация может расширить выделение новое выражение e1
обеспечить хранение для новое выражение e2 если …»

5

Другие решения

int *a, *b;
a = new int[10];
b = new int(12);
b = a; // I know there's memory leak, but let's ignore it first
delete [] b; // line L

Будет ли удаляться весь массив?

Да, память будет успешно освобождена, так как указатель a был установлен, чтобы указать на массив из 10 целых

a = new int[10];

затем тот же адрес ячейки памяти сохраняется в b

b = a;

Следовательно delete[] строка правильно освобождает кусок массива

delete [] b;

Как вы заявили, код также пропускает целочисленную переменную 12, которая была первоначально выделена и на которую указывает b,

Как звание sidenote delete[] освободить память, не связанную с массивом (особенно с несоответствием статического типа — см. [Expr.delete] / р3) вызвало бы неопределенное поведение.


Теперь немного о том, где хранится информация о размере при выделении памяти.

Компиляторы имеют некоторую степень свободы (см. §5.3.4 / 10), когда речь идет о запросах выделения памяти (например, они могут «группировать» запросы выделения памяти).

Когда вы звоните new без предоставленного распределителя, будет ли он вызывать malloc или нет, это определяется реализацией

[New.delete.single]

Выполняет цикл: внутри цикла функция сначала пытается выделить запрошенное хранилище.
Влечет ли попытка вызова функции библиотеки Standard C malloc, не уточняется.

Предполагая, что это вызывает malloc() (в современных системах Windows это называется UCRT), попытка выделить место означает ведение бухгалтерского учета с страничная виртуальная память, и это то, что обычно делает malloc.

Информация о вашем размере передается вместе с другими данными о том, как распределить запрошенную вами память.

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

Не существует «стандартного» способа получить эту информацию только с адреса, поскольку это деталь реализации. Как многие предлагали, если бы вам понадобилась такая информация, вы могли бы

  • хранить размер где-то
  • использование sizeof() с типом массива
  • еще лучше для C ++: используйте std::vector<>

Эта информация, кроме того, связана с распределением памяти, оба ваших вторичных случая (то есть замена L линия с

b = a + 1; delete [] b;

или же

a++; delete [] a;

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

2

Стандарт C ++ просто говорит, что используя delete[] по указателю, выделенному с newи используя delete по указателю, выделенному с new[] является неопределенным поведением.

Это может сработать, а может и нет, в зависимости от реализаций. Это может поджечь ваш дом.

Например, просто предположим, что эти функции основаны на базовом буфере с использованием malloc () и free ().

new а также delete в большинстве реализаций будет использовать буфер с точным размером и адресом элемента (здесь int)

new[] а также delete[] являются более сложными. Они должны хранить в буфере не только size предметы, но и ценность size, Реализация может хранить size до фактических предметов. Это будет означать, что указатель базового буфера не совпадает с указателем на первый элемент, который является значением, возвращаемым new[]

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

Конечно, все это определяется реализацией.

1
По вопросам рекламы [email protected]