ArrayRemove
Удаляет из массива указанное число элементов начиная с указанного индекса.
[in] Индекс, начиная с которого удаляются элементы массива.
[in] Количество удаляемых элементов. Значение WHOLE_ARRAY означает удаление всех элементов с указанного индекса до конца массива.
Возвращает true в случае успеха, иначе false. Чтобы получить информацию об ошибке, необходимо вызвать функцию GetLastError(). Возможные ошибки:
- 5052 – ERR_SMALL_ARRAY (значение start слишкое большое),
- 5056 – ERR_SERIES_ARRAY (массив не может быть изменен, индикаторный буфер),
- 4003 – ERR_INVALID_PARAMETER (значение count слишком большое),
- 4005 — ERR_STRUCT_WITHOBJECTS_ORCLASS (массив фиксированного размера, который содержит сложные объекты с деструктором),
- 4006 — ERR_INVALID_ARRAY (массив фиксированного размера, который содержит объекты структур или классов с деструктором).
Если функция используется для массива фиксированного размера, то сам размер массива не меняется: при этом происходит физическое копирование оставшегося «хвоста» в позицию start . Для точного понимания работы функции смотрите пример ниже. «Физическое» копирование означает, что копируемые объекты не создаются с помощью вызова конструктора или оператора копирования, а просто происходит копирование бинарного представления объекта. Именно по этой причине запрещается применять функцию ArrayRemove() к массиву фиксированного размера, содержащего объекты с деструктором (взводится ошибка ERR_INVALID_ARRAY или ERR_STRUCT_WITHOBJECTS_ORCLASS). Так как при удалении такого объекта деструктор должен быть вызван дважды – для первоначального объекта и его копии.
Нельзя удалять элементы из динамических массивов, назначенных в качестве индикаторных буферов функцией SetIndexBuffer(), это приведет к появлению ошибки ERR_SERIES_ARRAY. Для индикаторных буферов все операции по изменению размера производит исполняющая подсистема терминала.
//+——————————————————————+
//| Script program start function |
//+——————————————————————+
void OnStart ()
<
//— объявим массив фиксированного размера и заполним значениями
int array[10];
for ( int i=0;i <10;i++)
<
array[i]=i;
>
//— покажем массив до удаления элементов
Print ( «До вызова ArrayRemove()» );
ArrayPrint (array);
//— удалим 2 элемента из массива и покажем новый состав
ArrayRemove (array,4,2);
Print ( «После вызова ArrayRemove()» );
ArrayPrint (array);
/*
Результат выполнения:
До вызова ArrayRemove()
0 1 2 3 4 5 6 7 8 9
После вызова ArrayRemove()
0 1 2 3 6 7 8 9 8 9
*/
Оператор delete: удаление массива
Если вы выделяли память при помощи new[] , вы обязаны освободить её с помощью delete[] . Так гласит стандарт.
Освобождение такой памяти как-то по-другому ( delete без [] или вообще free ) является Undefined Behaviour. Если в программе есть Undefined Behaviour, она имеет право вести себя как угодно: может ничего плохого не делать, может вылететь в любой точке, отформатировать ваш винчестер или выбросить из окна вашего кота.
You have been warned.
Отслеживать
ответ дан 12 июн 2013 в 16:48
206k 28 28 золотых знаков 293 293 серебряных знака 526 526 бронзовых знаков
я понимаю что это по стандарту, но вот смотрите, тут после выполнения первого варианта и после второго. joxi.ru/uploads/prod/20130612/4a2/824/…
12 июн 2013 в 16:51
ну теперь понятнее, спасибо 🙂
12 июн 2013 в 16:54
Почитайте про undefined behaviour, забавная и опасная штука.
12 июн 2013 в 16:55
читаю, благодарю)
12 июн 2013 в 17:07
Палец вверх за кота 🙂
12 июн 2013 в 20:23
Если Вы выделили массив элементов, то этот массив и нужно удалить. New делает malloc на sizeof(объект) и вызывает конструктор объекта, new[] делает malloc на sizeof(объект) * кол-во_объектов, вызывает конструкторы для каждого будущего объекта и записывает информацию о том, память для скольких объектов была выделена. Куда и как эта информация записывается вопрос отдельный (плюс бывает ситуации когда это информация не нужна).
Вызывая delete Вы говорите компилятору «удали этот один элемент по такому-то адресу». Оператор delete[] же читает сколько объектов расположено в выделенной памяти (как мы помним, оператор new[] сохранил это число в процессе своей работы), вызывает для каждого их них деструктор, а после вызывает free() , «отдавая память назад ОС». Именно поэтому для памяти, выделенной через new/new[] нужно вызывать delete/delete[] соответственно. Контроль за тем, что для оператора выделения должен быть вызван соответствующий оператор освобождения лежит на программисте.
Отслеживать
ответ дан 13 июн 2013 в 9:02
3,456 12 12 серебряных знаков 9 9 бронзовых знаков
Оператор new вовсе не обязан по стандарту использовать malloc (он может, например, запросить прямо у системы без посредничества malloc ).
delete, new[] в C++ и городские легенды об их сочетании
Если в коде на C++ был создан массив объектов с помощью «new[]», удалять этот массив нужно с помощью «delete[]» и ни в коем случае не с помощью «delete» (без скобок). Разумный вопрос: а не то что?
На этот вопрос можно получить широчайший спектр неразумных ответов. Например, «будет удален только первый объект, остальные утекут» или «будет вызван деструктор только первого объекта». Следующие за этим «объяснения» не выдерживают обычно никакой серьезной критики.
В соответствии со Стандартом C++, в этой ситуации поведение не определено. Все предположения – не более чем популярные городские легенды. Разберем подробно, почему.
Нам понадобится хитрый план с примером, который бы ставил в тупик сторонников городских легенд. Вот такой безобидный будет ок:
class Class < public: ~Class() < printf( "Class::~Class()" ); >>; int main()
Здесь объект в массиве всего один. Если верить любой из двух легенд выше, «все будет хорошо» – утекать нечему и некуда, деструкторов будет вызвано ровно сколько нужно.
Идем на codepad.org, вставляем код в форму, получаем выдачу:
memory clobbered before allocated block Exited: ExitFailure 127 42 75 67 20 61 73 73 61 73 73 69 6E 20 77 61 6E 74 65 64 20 2D 20 77 77 77 2E 61 62 62 79 79 2E 72 75 2F 76 61 63 61 6E 63 79
MEMORY WHAT. Что это было?
int main()
No errors or program output.
Здесь хотя бы с виду все хорошо. Что происходит? Почему так происходит? Почему поведение с виду разное?
Причина в том, что происходит внутри.
Когда в коде встречается «new Type[count]», программа обязана выделить память объема, достаточного для хранения указанного числа объектов. Для этого она использует функцию «operator new[]()». Эта функция выделяет память – обычно внутри просто вызов malloc() и проверка возвращаемого значения (при необходимости – вызов new_handler() и выброс исключения). Затем в выделенной памяти конструируются объекты – вызывается нужное число конструкторов. Результатом «new Type[count]» является адрес первого элемента массива.
Когда в коде встречается «delete[] pointer», программа должна разрушить все объекты в массиве, вызвав для них деструкторы. Для этого (и только для этого) ей нужно знать число элементов.
Важный момент: в конструкции «new Type[count]» число элементов было указано явно, а «delete[]» получает только адрес первого элемента.
Откуда программа узнает число элементов? Раз у нее есть только адрес первого элемента, она должна вычислить длину массива на основании одного этого адреса. Как это делается, зависит от реализации, обычно используется следующий способ.
При выполнении «new Type[count]» программа выделяет памяти столько, чтобы в нее поместились не только объекты, но и беззнаковое целое (обычно типа size_t), обозначающее число объектов. В начало выделенной области пишется это число, дальше размещаются объекты. Компилятор при компиляции «new Type[count]» вставляет в программу код, который реализует эти свистелки.
Итак, при выполнении «new Type[count]» программа выделяет чуть больше памяти, записывает число элементов в начало выделенного блока памяти, вызывает конструкторы и возвращает вызывающему коду адрес первого элемента. Адрес первого элемента будет отличаться от адреса, который возвратила функция выделения памяти «operator new[]()».
При выполнении «delete[]» программа берет адрес первого элемента, переданный в «delete[]», определяет адрес начала блока (вычитая ровно столько же, сколько было прибавлено при выполнении «new[]»), читает число элементов из начала блока, вызывает нужное число деструкторов, затем – вызывает функцию «operator delete[]()», передав ей адрес начала блока.
В обоих случаях вызывающий код работает не с тем адресом, который был возвращен функцией выделения памяти и позже – передан функции освобождения памяти.
Теперь вернемся к первому примеру. Когда выполняется «delete» (без скобок), вызывающий код понятия не имеет, что нужно проиграть последовательность со смещением адреса. Скорее всего, он вызывает деструктор единственного объекта, затем передает в функцию «operator delete()» адрес, который отличается от ранее возвращенного функцией «operator new[]()».
Что должно произойти? В этой реализации программа аварийно завершается. Поскольку Стандарт говорит, что поведение не определено, это допустимо.
Для сравнения, программа на Visual C++ 9 по умолчанию исходит сообщениями об ошибках в отладочной версии, но вроде бы нормально отрабатывает (по крайней мере, функция _heapchk() возвращает код _HEAP_OK, _CrtDumpMemoryLeaks() не выдает никаких сообщений). Это тоже допустимо.
Почему во втором примере поведение другое? Скорее всего, компилятор учел, что у типа char тривиальный деструктор, т.е. не нужно ничего делать для разрушения объектов, а достаточно просто освободить память, поэтому и число элементов хранить не нужно, а значит, можно сразу вернуть вызывающему коду тот же адрес, который вернула функция «operator new[]()». Никаких смещений адреса – точно так же, как и при вызове «new» (без скобок). Такое поведение компилятора полностью соответствует Стандарту.
Чего-то не хватает…
Вы уже заметили, что выше по тексту встречаются функции выделения и освобождения памяти то с квадратными скобками, то без? Это не опечатки – это две разные пары функций, они могут быть реализованы совершенно по-разному. Даже когда компилятор пытается сэкономить, он всегда вызывает функцию «operator new[]()», когда видит в коде «new Type[count]», и всегда вызывает функцию «operator new()», когда видит в коде «new Type».
Обычно реализации функций «operator new()» и «operator new[]()» одинаковы (обе вызывают malloc()), но их можно заменить – определить свои, причем можно заменить как одну пару, так и обе, также можно заменять эти функции по отдельности для любого выбранного класса. Стандарт позволяет это делать сколько угодно (естественно, нужно адекватно заменить парную функцию освобождения памяти).
Это дает богатые возможности для неопределенного поведения. Если ваш код приводит к тому, что память освобождается «не той» функцией, это может приводить к любым последствиям, в частности, к повреждению кучи, порче памяти или немедленному аварийному завершению программы. В первом примере реализация функции «operator delete()» не смогла распорядиться переданным ей адресом и программа аварийно завершилась.
Самая приятная часть этого рассказа – вы никогда не сможете утверждать, что использование «delete» вместо «delete[]» (и наоборот – тоже) приводит к какому-то конкретному результату. Стандарт говорит, что поведение не определено. Даже полностью соответствующий Стандарту компилятор не обязан выдать вам программу с каким-либо адекватным поведением. Поведение программы, на которое вы будете ссылаться в комментариях и спорах, является только наблюдаемым – внутри может происходить все что угодно. Вы только констатируете наблюдаемое вами поведение.
Во втором примере с виду все хорошо… на этой реализации. На другой реализации функции «operator new()» и «operator new[]()» могут быть, например, реализованы на разных кучах (Windows позволяет создавать более одной кучи на процесс). Что произойдет при попытке возвратить блок «не в ту» кучу?
Кстати, рассчитывая на какое-то конкретное поведение в этой ситуации, вы автоматически получаете непереносимый код. Даже если на текущей реализации «все работает», при переходе на другой компилятор, при смене версии компилятора или даже при обновлении C++ runtime вы можете быть крайне неприятно удивлены.
Как быть? Смириться, не путать «delete» и «delete[]» и самое главное – не тратить зря время на «правдоподобные» объяснения того, что якобы произойдет, если вы их перепутаете. Пока вы будете спорить, другие разработчики будут делать что-то полезное, а для вас будет расти вероятность заслужить премию Дарвина.
Дмитрий Мещеряков
Департамент продуктов для разработчиков
Удаление элементов массива в JavaScript: оператор delete против метода splice
Часто в JavaScript приходится столкнуться с необходимостью удаления элементов из массива. В таких случаях на помощь приходят оператор delete и метод splice.
Алексей Кодов
Автор статьи
6 июля 2023 в 18:29
Часто в JavaScript приходится столкнуться с необходимостью удаления элементов из массива. В таких случаях на помощь приходят оператор delete и метод splice . Рассмотрим их на примере.
Допустим, есть массив цветов:
let colors = ['красный', 'синий', 'зеленый', 'желтый'];
И требуется удалить из него элемент ‘синий’. Это можно сделать двумя способами.
Оператор delete
Оператор delete удаляет свойство объекта. В случае массива, он удаляет элемент по указанному индексу:
delete colors[1];
Массив после этой операции будет выглядеть следующим образом:
['красный', undefined, 'зеленый', 'желтый']
Оператор delete не изменяет длину массива, а заменяет удаленный элемент на undefined .
Метод splice
Метод splice не только удаляет элемент, но и сдвигает остальные элементы массива, чтобы заполнить образовавшийся пробел:
colors.splice(1, 1);
Массив после этой операции будет выглядеть следующим образом:
['красный', 'зеленый', 'желтый']
Метод splice изменяет длину массива, полностью удаляя элемент.
Итог
Выбор между оператором delete и методом splice зависит от конкретной ситуации. Если нужно просто «обнулить» элемент, то подойдет delete . Если же требуется полностью удалить элемент и сдвинуть остальные, чтобы не было «дырки», то лучше использовать splice .