Структура динамического массива
Понятно, что они находтся в конце массива, но что они означают?
Ничего они не означают, это издержки прогресса. Современным процессорам и контроллерам памяти удобней работать, если данные выровнены на некоторую границу. Вот и выравнивают. Поэтому есть в компиляторах директива Align и функция SizeOf, возвращающая актуальный размер занимаемой памяти.
Возник ещё вопрос: код
var a: array of Byte;
begin
SetLength(a,5);//После этого массив занимает 22 байта
PInteger(Integer(a)-12)^:=18
PInteger(Integer(a)-4)^:=2;
//Копирование последних двух "ненужных" байтов - без этого не закрывается нормально процедура (выяснено методом тыка =))
PWord(Integer(a)+4)^:=PWord(Integer(a)+ 8 )^;
end;
эквивалентен
var a: array of Byte;
begin
SetLength(a,5);
SetLength(a,2);
end;
?
И будет ли после первого варианта корректно освобождена память - не потеряются ли 4 байта, которые были выделены процедурой SetLength? Как это можно проверить?
И будет ли после первого варианта корректно освобождена память - не потеряются ли 4 байта, которые были выделены процедурой SetLength?
Ты что-то не туда копаешь. Синдром, однако... Если сильно хочется узнать, как работает менеджер памяти в Дельфи, смотри исходники System.pas, SysInit.pas и getmem.inc. Там все написано.
Если сильно хочется узнать, как работает менеджер памяти в Дельфи, ...
Не совсем. Я хочу написать набор процедур, которые будут применятся к динамическим массивам вне зависимости от того, из каких элементов они состоят. Простейший пример: удаление n-го элемента - такая задача часто встаёт перед самыми разными массивами, и писать каждый раз новую процедуру - это неинтересно. Я откопал в System.pas процедуру DynArraySetLength, и если мне удастся приспособить её под свои нужды, то всё будет прекрасно. Сейчас стоит задача - найти способ получения TDynArrayTypeInfo, которое входит вторым параметром в эту процедуру.
найти способ получения TDynArrayTypeInfo, которое входит вторым параметром в эту процедуру.
TypeInfo(тип массива) - не катит?
TypeInfo(тип массива) - не катит?
Не совсем катит: выражения типа typeinfo(array of byte) оно не понимает, а каждый раз оформлять так:
type a = array of byte
typeinfo(a)
мне не нравится. Будет просто неудобно. Конечно, лучше, чем ничего, но я пока ищу обходные пути.
то компилятор знает тип переменной а, и у него есть вся необходимая мне информация - он её передаёт в процедуру SetLength - а я её получить не могу :(.
Посмотри по ассемблерному коду (Ctrl+Alt+C), откуда он ее берет.
push $0a
lea eax,[ebp-$04]
mov ecx,$0000001
mov edx,[$0040e914]
call @DynArraySetLength
Указатель $0040e914 как раз и содержит нужную мне информацию. Но мне непонятно, почему он оформлен как постоянная? Посоветуй, плиз, чем можно отыскать место его присвоения, а то я что-то не нашёл такой фичи, как поиск по ассемблерному коду. Да и, честно говоря, слабоват я в ассемблере. Кстати, постоянная разная после каждой постройки.
program Project1;
var a: array of byte;
begin
setlength(a,10);
end.
Запустил debug в W32Dasm и обнаружил, что уже на самом первом шаге эта информация хранится в том указателе. Теперь вообще теряюсь в догадках, когда же происходит присвоение значения. Может, это какой-то системный указатель?
Может, это какой-то системный указатель?
Это RTTI, она грузится при выполнении программы вместе с сегментами кода. Только вот, если это действительно указатель, где-то он должен инициализироваться, хоть в RTL.
var P: Pointer;
begin
P:=Pointer(Integer(Arr)-12);//сохраняем старый указатель, сдвигая его на начало структуры
GetMem(Arr,NewLength*ElSize+14);//выделяем память под новый массив.
//При этом не нужно беспокоится о правильном разбиении памяти на кратное 4 число байтов
Move(P^,Arr^,NewLength*ElSize+12);//копируем старый массив в новый
Inc(PInteger(P),3);//Возвращаемся к началу массива
Finalize(P);//И стираем его. Вот тут я не уверен, какое именно значение относительно начала
//структуры массива должна принимать переменная P, но в хедпе вроде так сказано
ElSize:=NewLength*ElSize;
PInteger(Arr)^:=ElSize+18-ElSize mod 4;//Выставляем размер памяти, которую занимает
//наша новая структура с учётом выравнивания и "лишних" последних двух байт.
PInteger(Integer(Arr)+8)^:=NewLength;//Дальнейшее оформление структуры
Inc(PInteger(Arr),3);//Возвращаем всё на свои места
end;
При этом необходимо, разумеется, сначала инициализировать массив через System.SetLength. В дальнейшем каждой процедуре, у которой в качестве параметра идёт произвольный массив, нужно передать только размер его элемента. Кроме того, могут возникнуть проблемы в случае, если на один массив ссылается более одной переменной. Но, думаю, исправить это при необходимости не составит труда.
Freeman, ещё раз огромное спасибо за помощь, теперь у меня всё работает как надо.
All, если кто заинтересуется полным кодом и моими дальнейшими наработками по этой теме, шлите мне сообщение по тому мылу, что я указал выше, с темой "ArrayRoutines.pas", и я его вам отошлю.
Всё, кажется, я решил задачу. Не найдя ни в сети, ни в хелпе информацию про адреса хранения RTTI
Вообще-то легальный доступ к RTTI можно получить через функции недоукментированного модуля TypInfo. Впрочем, по его исходнику практически все ясно.
А динамический вариант-массив не подходит для тех же целей? Хранит любые данные, динамически изменяет размер.
А динамический вариант-массив не подходит для тех же целей? Хранит любые данные, динамически изменяет размер.
1. Не так удобно - в моём варианте не требуется никаких дополнительных телодвижений, кроме указания размера элемента массива (впрочем, я, кажется, придумал, как от этого почти полностью избавится :) )
2. Не уверен, но могут возникнуть проблемы с записями. При этом пострадает удобство работы.
3. Опять же не уверен, но может пострадать быстродеействие. Впрочем, неизвестно ещё, какое будет быстродействие у моего кода :)
Главная причина, конечно, п.1.
1. Не так удобно - в моём варианте не требуется никаких дополнительных телодвижений, кроме указания размера элемента массива (впрочем, я, кажется, придумал, как от этого почти полностью избавится :) )
2. Не уверен, но могут возникнуть проблемы с записями. При этом пострадает удобство работы.
3. Опять же не уверен, но может пострадать быстродеействие. Впрочем, неизвестно ещё, какое будет быстродействие у моего кода :)
1. Использовать VarArrayCreate, VarArrayRedim не представляет особой сложности. Я, по крайней мере, не испытывал затруднений.
2. Тебе в твоем коде тоже придется изворачиваться с записями, при этом ошибок на первоначальном этапе избежать, скорее всего, не удастся.
3. Да, вариант требует некоторого веремени для приведения к конкретному типу, но удобно. :)
1. Использовать VarArrayCreate, VarArrayRedim не представляет особой сложности. Я, по крайней мере, не испытывал затруднений.
2. Тебе в твоем коде тоже придется изворачиваться с записями, при этом ошибок на первоначальном этапе избежать, скорее всего, не удастся.
3. Да, вариант требует некоторого веремени для приведения к конкретному типу, но удобно. :)
2. Не придётся. Записи в массиве располагаются в памяти последовательно, как и обычные элементы. Поэтому ничего дополнительно делать не надо. А ошибок избежать всё равно не удастся :). См. ниже.
1. Хм... посмотрел я тут эти процедуры (честно - не приходилось мне с этим раньше работать). В принципе можно их использовать так, как я хочу. Но опять же пострадают объявления: каждый массив придётся объявлять как один Variant и потом с ним работать как с Variant'ом. Не так красиво :)
З.Ы. У меня ошибка в процедуре SetLength - Finalize так не работает :) Вот вроде правильный вариант:
begin
Dec(PInteger(Arr),2);
ReallocMem(Arr,NewLength*ElSize+10);
Inc(PInteger(Arr));
PInteger(Arr)^:=NewLength;
Inc(PInteger(Arr));
end;
Как думаете, при этом не потеряются 4 байта адресом [Arr-12]? Если в первой строке исправить 2 на 3 и провести соответствующие изменения дальше - вылетает с ошибкой. А так - работает вроде...
З.З.Ы. Эх, учится, учится и ещё раз учится...
я так понял ты хочешь добиться этой процедурой универсальности, т.е. использовать для любых обьектов.
Я при использовании SetLengh натыкался на такие грабли,
возьмем такой код:
pSomeClass = ^TSomeClass;
TSomeClass = class;
...
var
a: array of TSomeClass;
pA : pSomeClass;
begin
setlength(a, 5);
a[0] := TSomeClass.Create;
...
a[4] := TSomeClass.Create;
pA := @a[3] ; // или другой индекс - неважно, как и размер 5.
setLength(a, 10);
// вот после указания нового размера указатель pA - либо работает нормально, либо.. "в яму упал :)"
end;
Т.е. получаеться что работает по разному иногда нормально, а иногда кричит про попытку чтения адресса. Предполагаю что происходит это из-за того самого ReallocMem (или что то в этом стиле в стандартной процедуре SetLength). Так вот немного поигравшись я перешел на (New(), Dispose()) организовывая свой динамический список - работает стабильно.
A SetLength можно использовать только если не требуеться хранить в какой-то другой переменной адрес на ячейку динамического массива.
Предполагаю что происходит это из-за того самого ReallocMem (или что то в этом стиле в стандартной процедуре SetLength).
Совершенно верно. См. процедуру DynArraySetLength в System.pas - у меня это 16062-я строка (Delphi 7).
И по поводу адреса на ячейку верно. Нужно чётко понимать, что ты делаешь кажой строчкой кода. Если используешь процедуру SetLength, то, соответственно, необходиимо проверять старые указатели.
Кстати, в твоём примере, по-моему, проще было бы объявить pA так: pA: TSomeClass; и делать такое присвоение: pA:=a[3]; Тогда бы таких глюков не было, а функциональность сохранилась.
Если используешь процедуру SetLength, то, соответственно, необходиимо проверять старые указатели.
Кстати, в твоём примере, по-моему, проще было бы объявить pA так: pA: TSomeClass; и делать такое присвоение: pA:=a[3]; Тогда бы таких глюков не было, а функциональность сохранилась.
После каждого изменения размера переприсваивать указатели? - некрасиво получиться.
--
pA: TSomeClass;
pA:=a[3];
Я в курсе... и что меня устраивает так это то что при изменении полей pA они меняються и в a[3], следовательно pA:=a[3] - передаеться указатель на память, и он действительно стабильный (мне не удалось никаким колличеством SetLength его потерять... ) интересно, почему так? Но, дело обстоит совсем по другому если в место TSomeClass взять record... тогда при изменении полей в pA, в a[3] они остаються прежние ! в таком случае, что бы не дублировать лишний раз память, прийдеться использовать указатель на record... я и так попробывал:
pRec =^Trec;
Trec = record
...
a : array of Trec;
pa : pRec;
pr : Trec;
...
Pa := @a[3];// передаем указатель на ячейку.
pr := a[3]; // практически копируем ячейку массива a[3] в новый кусок памяти.
---
И опять не смог потерять указатель Pa!!! После множества изменений размера A...
...и он действительно стабильный (мне не удалось никаким колличеством SetLength его потерять... ) интересно, почему так?
Дык pA и a[3] у тебя указывают на одну и ту же структуру в памяти. Разумеется, как ни меняй длину массива, значения его элементов ты не изменишь.
А вот это уже странно. Если используешь процедуру SetLength, увеличивая длину, и массив не помещается в старом куске памяти, то адрес должен быть другим. Только я не понял, зачем нужны сразу и pA, и pr. Но без конкретной задачи что-то дельное сказать здесь сложно. Если хочешь - сформулируй запрос, я постараюсь что-нибудь посоветовать. А если просто поговорить :) - ну что же, я согласен, бывают разные методы...