Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Структура динамического массива

11K
24 июня 2005 года
Jini
16 / / 24.06.2005
Насколько я понял, динамический массив имеет такую структуру: он начинается за 12 байт до указателя и сначала идёт размер памяти, который он занимает, потом - счётчик ссылок на этот массив и его длина. После этого располагаются элементы массива. При этом почему-то на эти элементы всегда выделяется количество памяти, кратное 4. Кроме того, оказывается, что массив из одного Byte, также, как и из одного Integer, занимают одинаковое количество памяти - 18 байт. Вопрос: куда деваются ещё два байта? Понятно, что они находтся в конце массива, но что они означают? Кроме того, меня интересует, как с помощью одного указателя на массив можно получить размер элемента этого массива? И ещё: если у кого-нибудь найдётся описание или, лучше, исходный код к процедуре SetLength, поделитесь, пожалуйста. Можно на почту: jini_( собака )mail( точка )ru
10
25 июня 2005 года
Freeman
3.2K / / 06.03.2004
Цитата:
Originally posted by Jini
Понятно, что они находтся в конце массива, но что они означают?


Ничего они не означают, это издержки прогресса. Современным процессорам и контроллерам памяти удобней работать, если данные выровнены на некоторую границу. Вот и выравнивают. Поэтому есть в компиляторах директива Align и функция SizeOf, возвращающая актуальный размер занимаемой памяти.

11K
25 июня 2005 года
Jini
16 / / 24.06.2005
Странно, почему тогда вся выделенная память не выравнивается на 4, только память под массив.

Возник ещё вопрос: код
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? Как это можно проверить?
10
25 июня 2005 года
Freeman
3.2K / / 06.03.2004
Цитата:
Originally posted by Jini
И будет ли после первого варианта корректно освобождена память - не потеряются ли 4 байта, которые были выделены процедурой SetLength?


Ты что-то не туда копаешь. Синдром, однако... Если сильно хочется узнать, как работает менеджер памяти в Дельфи, смотри исходники System.pas, SysInit.pas и getmem.inc. Там все написано.

11K
25 июня 2005 года
Jini
16 / / 24.06.2005
Цитата:
Originally posted by Freeman
Если сильно хочется узнать, как работает менеджер памяти в Дельфи, ...



Не совсем. Я хочу написать набор процедур, которые будут применятся к динамическим массивам вне зависимости от того, из каких элементов они состоят. Простейший пример: удаление n-го элемента - такая задача часто встаёт перед самыми разными массивами, и писать каждый раз новую процедуру - это неинтересно. Я откопал в System.pas процедуру DynArraySetLength, и если мне удастся приспособить её под свои нужды, то всё будет прекрасно. Сейчас стоит задача - найти способ получения TDynArrayTypeInfo, которое входит вторым параметром в эту процедуру.

10
25 июня 2005 года
Freeman
3.2K / / 06.03.2004
Цитата:
Originally posted by Jini
найти способ получения TDynArrayTypeInfo, которое входит вторым параметром в эту процедуру.


TypeInfo(тип массива) - не катит?

11K
25 июня 2005 года
Jini
16 / / 24.06.2005
Цитата:
Originally posted by Freeman
TypeInfo(тип массива) - не катит?


Не совсем катит: выражения типа typeinfo(array of byte) оно не понимает, а каждый раз оформлять так:
type a = array of byte
typeinfo(a)
мне не нравится. Будет просто неудобно. Конечно, лучше, чем ничего, но я пока ищу обходные пути.

11K
26 июня 2005 года
Jini
16 / / 24.06.2005
Вот ведь обидно - если объявить так: var a: array of byte; то компилятор знает тип переменной а, и у него есть вся необходимая мне информация - он её передаёт в процедуру SetLength - а я её получить не могу :(. Ладно, буду работать с тем, что есть. Огромное спасибо тебе за внимание. Потому что только формулируя свои вопросы я уже наполовину получал ответы. :) Если таки найду решение поизящнее, напишу.
10
26 июня 2005 года
Freeman
3.2K / / 06.03.2004
Цитата:
Originally posted by Jini
то компилятор знает тип переменной а, и у него есть вся необходимая мне информация - он её передаёт в процедуру SetLength - а я её получить не могу :(.


Посмотри по ассемблерному коду (Ctrl+Alt+C), откуда он ее берет.

11K
26 июня 2005 года
Jini
16 / / 24.06.2005
Там выполняется такой код:
push $0a
lea eax,[ebp-$04]
mov ecx,$0000001
mov edx,[$0040e914]
call @DynArraySetLength
Указатель $0040e914 как раз и содержит нужную мне информацию. Но мне непонятно, почему он оформлен как постоянная? Посоветуй, плиз, чем можно отыскать место его присвоения, а то я что-то не нашёл такой фичи, как поиск по ассемблерному коду. Да и, честно говоря, слабоват я в ассемблере. Кстати, постоянная разная после каждой постройки.
11K
26 июня 2005 года
Jini
16 / / 24.06.2005
Облазил тут WinHex'ом оперативную память, отведённую под процесс - сочетание байтов 14e94000 (этот указатель в машинном представлении, насколько я понимаю) встречается два раза и первый из них - в этой процедуре SetLength, а второй - кажется, у неё внутри, но он там участвует в той же команде - 8b1514e94000. Есть идеи?
11K
26 июня 2005 года
Jini
16 / / 24.06.2005
Поставил W32Dasm, деассемблировал свой Project1.exe, в котором был вот такой код:
program Project1;
var a: array of byte;
begin
setlength(a,10);
end.
Запустил debug в W32Dasm и обнаружил, что уже на самом первом шаге эта информация хранится в том указателе. Теперь вообще теряюсь в догадках, когда же происходит присвоение значения. Может, это какой-то системный указатель?
10
26 июня 2005 года
Freeman
3.2K / / 06.03.2004
Цитата:
Originally posted by Jini
Может, это какой-то системный указатель?


Это RTTI, она грузится при выполнении программы вместе с сегментами кода. Только вот, если это действительно указатель, где-то он должен инициализироваться, хоть в RTL.

11K
28 июня 2005 года
Jini
16 / / 24.06.2005
Всё, кажется, я решил задачу. Не найдя ни в сети, ни в хелпе информацию про адреса хранения RTTI и способы их получения через имя переменной, я решил пойти по-другому и переписал функцию SetLength под свои нужды. Выглядит она теперь так:
Код:
procedure mySetLength(var Arr: Pointer; ElSize,NewLength: Integer);
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", и я его вам отошлю.
10
28 июня 2005 года
Freeman
3.2K / / 06.03.2004
Цитата:
Originally posted by Jini
Всё, кажется, я решил задачу. Не найдя ни в сети, ни в хелпе информацию про адреса хранения RTTI


Вообще-то легальный доступ к RTTI можно получить через функции недоукментированного модуля TypInfo. Впрочем, по его исходнику практически все ясно.

11K
28 июня 2005 года
Jini
16 / / 24.06.2005
Всё дело в том, что мне нужно было получить typeinfo динамического массива, не объявленного как отдельный тип. Компилятор не понимает выражения типа "typeinfo(array of byte)". Кстати, этот модуль я тоже просмотрел - почти все его функции и процедуры относятся к объектам или требуют typeinfo в качестве параметра.
302
28 июня 2005 года
Sagittarius
648 / / 12.04.2003
Устроили тут трепанацию System.pas. :)
А динамический вариант-массив не подходит для тех же целей? Хранит любые данные, динамически изменяет размер.
11K
28 июня 2005 года
Jini
16 / / 24.06.2005
Цитата:
Originally posted by Sagittarius
А динамический вариант-массив не подходит для тех же целей? Хранит любые данные, динамически изменяет размер.


1. Не так удобно - в моём варианте не требуется никаких дополнительных телодвижений, кроме указания размера элемента массива (впрочем, я, кажется, придумал, как от этого почти полностью избавится :) )
2. Не уверен, но могут возникнуть проблемы с записями. При этом пострадает удобство работы.
3. Опять же не уверен, но может пострадать быстродеействие. Впрочем, неизвестно ещё, какое будет быстродействие у моего кода :)
Главная причина, конечно, п.1.

302
29 июня 2005 года
Sagittarius
648 / / 12.04.2003
Цитата:
Originally posted by Jini
1. Не так удобно - в моём варианте не требуется никаких дополнительных телодвижений, кроме указания размера элемента массива (впрочем, я, кажется, придумал, как от этого почти полностью избавится :) )
2. Не уверен, но могут возникнуть проблемы с записями. При этом пострадает удобство работы.
3. Опять же не уверен, но может пострадать быстродеействие. Впрочем, неизвестно ещё, какое будет быстродействие у моего кода :)


1. Использовать VarArrayCreate, VarArrayRedim не представляет особой сложности. Я, по крайней мере, не испытывал затруднений.
2. Тебе в твоем коде тоже придется изворачиваться с записями, при этом ошибок на первоначальном этапе избежать, скорее всего, не удастся.
3. Да, вариант требует некоторого веремени для приведения к конкретному типу, но удобно. :)

11K
29 июня 2005 года
Jini
16 / / 24.06.2005
Цитата:
Originally posted by Sagittarius
1. Использовать VarArrayCreate, VarArrayRedim не представляет особой сложности. Я, по крайней мере, не испытывал затруднений.
2. Тебе в твоем коде тоже придется изворачиваться с записями, при этом ошибок на первоначальном этапе избежать, скорее всего, не удастся.
3. Да, вариант требует некоторого веремени для приведения к конкретному типу, но удобно. :)


2. Не придётся. Записи в массиве располагаются в памяти последовательно, как и обычные элементы. Поэтому ничего дополнительно делать не надо. А ошибок избежать всё равно не удастся :). См. ниже.
1. Хм... посмотрел я тут эти процедуры (честно - не приходилось мне с этим раньше работать). В принципе можно их использовать так, как я хочу. Но опять же пострадают объявления: каждый массив придётся объявлять как один Variant и потом с ним работать как с Variant'ом. Не так красиво :)

З.Ы. У меня ошибка в процедуре SetLength - Finalize так не работает :) Вот вроде правильный вариант:

 
Код:
procedure SetLength(var Arr: Pointer; ElSize,NewLength: Integer);
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 и провести соответствующие изменения дальше - вылетает с ошибкой. А так - работает вроде...

З.З.Ы. Эх, учится, учится и ещё раз учится...
6.7K
29 июня 2005 года
Metalslave
37 / / 24.08.2004
Jini, может я и не совсем по теме, извиняй, но
я так понял ты хочешь добиться этой процедурой универсальности, т.е. использовать для любых обьектов.
Я при использовании 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 можно использовать только если не требуеться хранить в какой-то другой переменной адрес на ячейку динамического массива.
11K
29 июня 2005 года
Jini
16 / / 24.06.2005
Цитата:
Originally posted by Metalslave
Предполагаю что происходит это из-за того самого ReallocMem (или что то в этом стиле в стандартной процедуре SetLength).


Совершенно верно. См. процедуру DynArraySetLength в System.pas - у меня это 16062-я строка (Delphi 7).
И по поводу адреса на ячейку верно. Нужно чётко понимать, что ты делаешь кажой строчкой кода. Если используешь процедуру SetLength, то, соответственно, необходиимо проверять старые указатели.

Кстати, в твоём примере, по-моему, проще было бы объявить pA так: pA: TSomeClass; и делать такое присвоение: pA:=a[3]; Тогда бы таких глюков не было, а функциональность сохранилась.

6.7K
30 июня 2005 года
Metalslave
37 / / 24.08.2004
Цитата:
Originally posted by Jini
Если используешь процедуру 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...

11K
30 июня 2005 года
Jini
16 / / 24.06.2005
Цитата:
Originally posted by Metalslave
...и он действительно стабильный (мне не удалось никаким колличеством SetLength его потерять... ) интересно, почему так?


Дык pA и a[3] у тебя указывают на одну и ту же структуру в памяти. Разумеется, как ни меняй длину массива, значения его элементов ты не изменишь.

Цитата:
И опять не смог потерять указатель Pa!!! После множества изменений размера A...


А вот это уже странно. Если используешь процедуру SetLength, увеличивая длину, и массив не помещается в старом куске памяти, то адрес должен быть другим. Только я не понял, зачем нужны сразу и pA, и pr. Но без конкретной задачи что-то дельное сказать здесь сложно. Если хочешь - сформулируй запрос, я постараюсь что-нибудь посоветовать. А если просто поговорить :) - ну что же, я согласен, бывают разные методы...

Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог