Указатель на массив.

Вопросы программирования на Free Pascal, использования компилятора и утилит.

Модератор: Модераторы

Указатель на массив.

Сообщение Maxizar » 05.02.2011 20:15:47

Вот есть такие типы данных:
Код: Выделить всё
    TRealArray = array of Real;
    PRealArray=^TRealArray; 


Задача: Прочитав данные из TMemoryStream, обработать их и поместить в переменную типа TRealArray, и положить в Tlist.
Вот код (оставил только логику)
Код: Выделить всё
function F1(const AData: TMemoryStream): TRealArray;   
begin
Result:=Nil;
   if (AData=nil) then exit;

   SetLength(Result,N);
//далее работаем с созданным массивом, заполняем его данными
end;

procedure AddData1(const AData: TMemoryStream);
var NewData:PRealArray;
begin
  if (AData=nil) or (AData.Size<=FCountPoint) then exit;

   NewData:=New(PRealArray);
   NewData^:= F1(AData);
   if NewData^=nil then
   begin
     Dispose(NewData);
     Exit;
   end;
   
   Data.Add(NewData);
end;     
//процедура проверки полученных данных (вывод в файл)
procedure ShowResult;
  var
  FText:TextFile;
  SData:TRealArray;
Begin
if Data[0]=nil then
       exit;

      AssignFile(FText,'C:\Spectr.txt');
      Rewrite(FText);
      SData:=PRealArray(Data[0])^;

      for I:=1 to High(SData) do
       Writeln(FText,FloatToStr(SData[I]));
     
      CloseFile(FText);
     
       //Data[0]:=nil;
      //dispose(PRealArray(Data[0]));
End;


Проверил, все хорошо работает файл и данные верные.
Вопрос: в конце процедуры ShowResult, закомментированы две строчки, которые должны будут освободить память, какую применять более верно.?
Т.к используем выделение памяти при помощи New. То dispose более верно, это так?

Вариант 2:
Ведь можно переписать процедуры так:
Код: Выделить всё
procedure AddData1(const AData: TMemoryStream);
var NewData:TRealArray;
begin
  if (AData=nil) or (AData.Size<=FCountPoint) then exit;

   NewData:= F1(AData);
   if NewData<>nil then
   Data.Add(@NewData);     //Вот тут сомнения так ли?
                                             //может нужно так Data.Add(@NewData[0])

end;     
//процедура проверки полученных данных (вывод в файл)
procedure ShowResult;
  var
  FText:TextFile;
  SData:TRealArray;
Begin
if Data[0]=nil then
       exit;

      AssignFile(FText,'C:\Spectr.txt');
      Rewrite(FText);
      SData:=TRealArray(Data[0]^);

      for I:=1 to High(SData) do
       Writeln(FText,FloatToStr(SData[I]));
     
      CloseFile(FText);
     
       //Data[0]:=nil;
      //dispose(PRealArray(Data[0]));
End;

Процедура AddData1 уменьшилась и стала более наглядной, но вот в ShowResult в SData передается мусор, (что не так я делаю?). Возможно из-за того, что в процедуре ShowResult мы использовали не указатель, а локальную переменную (мое предположение) вследствие чего в Data.Add(@NewData); записали указатель, на массив, который потом был удален.
т.е остается верным только первый варинат?

PS. Что то я запутался :)
Maxizar
постоялец
 
Сообщения: 385
Зарегистрирован: 20.03.2010 19:48:14

Re: Указатель на массив.

Сообщение Odyssey » 05.02.2011 22:27:33

Maxizar писал(а):Т.к используем выделение памяти при помощи New. То dispose более верно, это так?

В целом - так. Если указателю просто присвоить nil, память выделенная под него, останется неосвобождённой.
Maxizar писал(а):но вот в ShowResult в SData передается мусор ... Возможно из-за того, что в процедуре ShowResult мы использовали не указатель, а локальную переменную ... вследствие чего в Data.Add(@NewData); записали указатель, на массив, который потом был удален.

Точно. Динамические массивы -- это тип с автоматическим подсчётом ссылок ("управляемый"). Т.е. пользователю остаётся только делать SetLength, а остальные операции с памятью рулятся RTL.

И, кстати, поэтому мне не нравится первый вариант, т.к. там перемешаны управляемые и неуправляемые типы:
Код: Выделить всё
function F1(const AData: TMemoryStream): TRealArray;   
...
procedure AddData1(const AData: TMemoryStream);
var NewData:PRealArray;
begin
   NewData:=New(PRealArray);
   NewData^:= F1(AData);

Я слабо представляю что происходит в последней строчке, а именно, как работает подсчёт ссылок -- реагирует он на работу разыменованным указателем или нет. Если не реагирует -- то возвращённый из F1 массив должен сразу же освободиться как неиспользуемый. Возможно, так и происходит, просто память в куче по этому адресу на момент чтения ещё никто не успевает занять, поэтому всё работает по случайному совпадению, до следующего более-менее крупного изменения в программе. А если подсчёт ссылок реагирует на конструкцию "NewData^:=" увеличением числа ссылок, то всё должно работать.

Поэтому я бы тут, наверное, не стал бы мешать TRealArray и PRealArray, использовал бы только PRealArray, причём объявить его можно просто как указатель на Real (см. тут). Или, по крайней мере, узнал про поведение подсчёта ссылок.

Maxizar писал(а)://Вот тут сомнения так ли? может нужно так Data.Add(@NewData[0])

Нужно, потому что, @NewData, насколько я понял, хранит длину массива, а @NewData[0] -- это адрес первого элемента. Проблема в том, что подсчёт ссылок в этом случае не работает, и массив освободиться как только исчезнет последняя использующая его переменная, а в TList останется левый указатель.
Последний раз редактировалось Odyssey 05.02.2011 23:37:51, всего редактировалось 2 раз(а).
Odyssey
энтузиаст
 
Сообщения: 580
Зарегистрирован: 29.11.2007 17:32:24

Re: Указатель на массив.

Сообщение Maxizar » 05.02.2011 23:14:15

Odyssey - В какой раз, Вы уже направляете меня на правильный путь (Спасибо за это :) ). Вроде пришло некоторое понимание, вследствие чего пришел к такому решению:
1 – Переписываем функцию F1 так:
Код: Выделить всё
function F1(const AData: TMemoryStream): PRealArray;   
begin
Result:=Nil;
   if (AData=nil) then exit;

   Result:=New(PRealArray);
   SetLength(Result^,N);
//далее работаем с созданным массивом, заполняем его данными
end;

Это приводит к тому, что мы передав битый AData, вернем просто nil. Если же AData «хороший», мы вправе сгенерировать результат. Для этого создаем указатель (он будет жить до тех пор пока мы не вызовем dispose). И с ссылками вроде все ок.

2 – Модификация процедуры добавления в листинг:
Код: Выделить всё
procedure AddData1(const AData: TMemoryStream);
var NewData:PRealArray;
begin
  if (AData=nil) or (AData.Size<=FCountPoint) then exit;

NewData:= F1(AData);
If  NewData <> nil then
Data.Add(NewData);  //добавили валидный указатель на массив.
end;     

3- модификация процедуры ShowResult:
Код: Выделить всё
procedure ShowResult;
  var
  FText:TextFile;
  SData:TRealArray;
Begin
if Data[0]=nil then     exit;

      AssignFile(FText,'C:\Spectr.txt');
      Rewrite(FText);
      SData:=PRealArray(Data[0])^;

      for I:=1 to High(SData) do
       Writeln(FText,FloatToStr(SData[I]));
     
      CloseFile(FText);
     
      SetLength(SData,0);                //обнуляем длину
      dispose(PRealArray(Data[0])); //освобождаем указатель и связанную с ним ОЗУ
      Data[0]:=nil;                             //чтоб больше не указывать на несуществующий массив
End;

В конце получается, законное применение процедуры dispose, которое и освободит память.
Вроде бы все правильно… Кто, что скажет?.
Maxizar
постоялец
 
Сообщения: 385
Зарегистрирован: 20.03.2010 19:48:14

Re: Указатель на массив.

Сообщение Иван Шихалев » 05.02.2011 23:31:17

Какой тип у Data? Вообще, приведение к указателю — оно зачем?
Подсчет ссылок с разыменованием — штука подозрительная (хоть вроде и работает). Но лучше, вероятно, обернуть массив в класс — спокойнее будет.
Аватара пользователя
Иван Шихалев
энтузиаст
 
Сообщения: 1138
Зарегистрирован: 15.05.2006 11:26:13
Откуда: Екатеринбург

Re: Указатель на массив.

Сообщение Odyssey » 06.02.2011 00:01:23

Мда, теперь мне не нравится вот это:
Код: Выделить всё
SetLength(Result^,N);

По документации, SetLength не предназначена для использования таким образом. Она должна работать только со строками и динамическими массивами (т.е., по сути, принимает специальный указатель на внутреннюю структуру данных). А мы ей подсовываем указатель на обычный Real, и как она будет с ним работать -- неизвестно.
Odyssey писал(а):я бы тут, наверное, не стал бы мешать TRealArray и PRealArray, использовал бы только PRealArray

Это я не очень обдуманно сказал :( Поскольку массив динамический, а New выделяет память только под один элемент, тут вместо New/Dispose придётся использовать GetMem/FreeMem, у них можно указывать размер. А что хуже всего, этот размер придётся куда-то записывать после вызова GetMem, чтобы потом передать его во FreeMem.

Иван Шихалев прав, способ с оборачиванием массива в класс -- самый надёжный. Это немного больше кода, зато гарантированная работа и автоматическое управление памятью. Плюс вместо TList можно использовать TFPObjectList, тогда список сам освободит классы-обёртки при своём уничтожении.

P.S.
Или, если есть желание связываться с шаблонами, можно было бы специализировать класс-список Real'ов.
Odyssey
энтузиаст
 
Сообщения: 580
Зарегистрирован: 29.11.2007 17:32:24

Re: Указатель на массив.

Сообщение Иван Шихалев » 06.02.2011 00:05:52

Odyssey писал(а):А что хуже всего, этот размер придётся куда-то записывать после вызова GetMem, чтобы потом передать его во FreeMem.

Не нужно. FreeMem с одним параметром освобождает столько, сколько было выделено GetMem.
Аватара пользователя
Иван Шихалев
энтузиаст
 
Сообщения: 1138
Зарегистрирован: 15.05.2006 11:26:13
Откуда: Екатеринбург

Re: Указатель на массив.

Сообщение Maxizar » 06.02.2011 00:36:46

Т.е я правильно понял: нужно создать класс, в котором будет поле – массив, и уже обмениваться ссылками на этот класс.
Иван Шихалев писал(а):Какой тип у Data?

Data это Tlist, который содержит указатели на массивы.
Odyssey писал(а):Поскольку массив динамический, а New выделяет память только под один элемент, тут вместо New/Dispose придётся использовать GetMem/FreeMem, у них можно указывать размер.

Т.е Вы хотите сказать, что мне повезло. я создал указатель и после него, память была пуста. Из-за чего работа как бы прошла на хорошо.
Но ведь New- создает указатель на область памяти для хранения переменной. И лишь когда мы разыменовываем его и скажем применяем конструктор, то начиная с этого места и будет находится наш класс, массив и т.п.
Т.е:
Код: Выделить всё
PE=^E;
Type E=class
//бла бла бла
End;

Var nE:PE;
Begin
nE:=New(PE);//Создали указатель в памяти
nE^:=E.Create; //Теперь начиная с этого указателя(с ячейки памяти) находится наш класс типа E, на который и указывает nE.
End;


Потом, почему возникает подозрение на вот этот код:
Код: Выделить всё
SetLength(Result^,N);


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

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

Почему именно так, потому что в дальнейшем нужно будет переписывать код добавления величин в массив на Асме, критично по времени. А с классами я наверное не осилю переписать процедуру под Асм :( .

Мда, задачку я себя задал, наверное не по рангу.

Спасибо ребята, что ответили. Буду думать дальше.
Maxizar
постоялец
 
Сообщения: 385
Зарегистрирован: 20.03.2010 19:48:14

Re: Указатель на массив.

Сообщение devels » 06.02.2011 17:53:33

С чего это вдруг нельзя получить размер памяти динамического массива?

Код: Выделить всё
Result := SizeOf(Real) * Length(Arr);


А вообще желательно начать с того, что тебе вообще нужно? Чую что ты делаешь через одно место, когда можно проще. Я не понимаю зачем тебе вообще использовать TList.
devels
постоялец
 
Сообщения: 137
Зарегистрирован: 01.09.2010 12:14:38


Вернуться в Free Pascal Compiler

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и гости: 5

Рейтинг@Mail.ru