Расположение указателей на сложные записи - PullRequest
4 голосов
/ 06 августа 2011

У меня есть список указателей на некоторые сложные записи. Иногда, когда я пытаюсь избавиться от них, я получаю неверную ошибку указателя. Я не совсем уверен, правильно ли я их создаю и утилизирую. Запись выглядит так:

type
  PFILEDATA = ^TFILEDATA;
  TFILEDATA = record
    Description80: TFileType80;  // that's array[0..80] of WideChar
    pFullPath: PVeryLongPath;    // this is pointer to array of WideChar
    pNext: PFILEDATA;            // this is pointer to the next TFILEDATA record
  end;

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

function GimmeNewData(): PFILEDATA;
begin
  New(Result);
  New(Result^.pFullPath);
end;

Теперь, чтобы избавиться от ряда этих записей, я написал это:

procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
  while pData^.pNext <> nil do begin
    pNextData := pData^.pNext;          // Save pointer to the next record
    Finalize(pData^.pFullPath);         // Free dynamic array
    Dispose(pData);                     // Free the record
    pData := pNextData;
  end;
  Finalize(pData^.pFullPath);
  Dispose(pData);
  pData := nil;
end;

Когда я запускаю свою программу в режиме отладки (F9) в Delphi 2010 IDE, происходит что-то странное. Когда я делаю код DisposeData с помощью F8, кажется, что программа пропускает строку Finalize (pData ^ .pFullPath) и переходит к Dispose (pData). Это нормально? Также при выполнении Dispose (pData) окно Local переменных, в котором отображается содержимое указателей, не изменяется. Означает ли это, что утилизация не удалась?

Edit: PVeryLongPath - это:

type
  TVeryLongPath = array of WideChar;
  PVeryLongPath = ^TVeryLongPath;

Edit2

Итак, я создаю 2 записи TFILEDATA, а затем уничтожаю их. Затем я снова создаю те же 2 записи. Почему-то на этот раз pNext во второй записи не равно нулю. Это указывает на 1-ую запись. Уничтожение этой странной вещи приводит к ошибке операции с недействительным указателем. Случайно я вставил pData ^ .pNext: = nil в процедуру DisposeData. Теперь код выглядит так:

procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
  while pData <> nil do begin
    pNextData := pData^.pNext;
    pData^.pNext := nil;          // <----
    Dispose(pData^.pFullPath);
    Dispose(pData);
    pData := pNextData;
  end;
end;

Ошибка ушла. Я попытаюсь изменить PVeryLongPath на TVeryLongPath.

Ответы [ 5 ]

5 голосов
/ 06 августа 2011

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

РЕДАКТИРОВАТЬ: объявить pFullPath как TVeryLongPath. Это уже ссылочный тип, и вы не должны использовать указатель на такой тип. New () не делает то, что вы думаете, в таком случае.

Вероятно, было бы лучше, если бы вы объявили его как UnicodeString или если ваш Delphi не имеет его, WideString.

Если pFullPath объявлен как динамический «массив WideChar», то вы не должны использовать New () для него. Для динамических массивов используйте SetLength () и ничего больше. Dispose () будет правильно удалять все элементы в вашей записи, поэтому просто сделайте:

New(Result);
SetLength(Result^.pFullPath, size_you_need);

и позже:

Dispose(pData);

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

FWIW, я бы порекомендовал это и это мою статью.

4 голосов
/ 06 августа 2011

Тот факт, что вы приняли ответ Серга, указывает на то, что с кодом создания вашего узла что-то не так. Ваш комментарий к этому ответу подтверждает это.

Я добавляю это как новый ответ, потому что изменения в вопросе существенно меняют его.

Код связанного списка должен выглядеть следующим образом:

var
  Head: PNode=nil;
  //this may be a global variable, or better, a field in a class, 
  //in which case it would be initialized to nil on creation

function AddNode(var Head: PNode): PNode;
begin
  New(Result);
  Result.Next := Head;
  Head := Result;
end;

Обратите внимание, что мы добавляем узел в начало списка. Нам не нужно инициализировать Next до nil где-либо, потому что мы всегда назначаем указатель другого узла на Next. Это правило важно.

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

Иногда вам может потребоваться инициализировать содержимое узла при добавлении новых узлов. Например:

function AddNode(var Head: PNode; const Caption: string): PNode;
begin
  New(Result);
  Result.Caption := Caption;
  Result.Next := Head;
  Head := Result;
end;

Я очень предпочитаю такой подход. Всегда убедитесь, что ваши поля инициализированы. Если для вас подходит нулевая инициализация, вы можете использовать AllocMem для создания вашего узла.

Вот более конкретный пример использования такого метода:

type
  PNode = ^TNode;
  TNode = record
    Caption: string;
    Next: PNode;
  end;

procedure PopulateList(Items: TStrings);
var
  Item: string;
begin
  for Item in Items do
    AddNode(Head, Item);
end;

Чтобы уничтожить список, код работает так:

procedure DestroyList(var Head: PNode);
var
  Next: PNode;
begin
  while Assigned(Head) do begin
    Next := Head.Next;
    Dispose(Head);
    Head := Next;
  end;
end;

Вы можете ясно видеть, что этот метод может возвращаться только тогда, когда Head равен nil.

Если вы инкапсулируете свой связанный список в классе, тогда вы можете сделать указатель заголовка членом класса и избежать необходимости его передачи.

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

4 голосов
/ 06 августа 2011

Я рекомендую вам избегать использования динамического массива WideChar, с которым вообще не удобно работать.Вместо этого используйте string, если у вас Delphi 2009 или более поздняя версия, или WideString для более ранних версий Delphi.Оба они являются динамическими строковыми типами с WideChar элементами.Вы можете назначить им, и Delphi обрабатывает все распределение.

Итак, предполагая, что теперь у вас есть следующая запись:

TFILEDATA = record
  Description80: TFileType80;
  pFullPath: WideString; 
  pNext: PFILEDATA;          
end;

, вы можете значительно упростить вещи.

function GimmeNewData(): PFILEDATA;
begin
  New(Result);
end;

procedure DisposeData(var pData: PFILEDATA);
var pNextData: PFILEDATA;
begin
  while pData <> nil do begin
    pNextData := pData^.pNext;          
    Dispose(pData);                     
    pData := pNextData;
  end;
end;
1 голос
/ 06 августа 2011

Я думаю, что большинство ваших проблем вызвано предположением, что New() дает вам обнуленную память. Я почти уверен (и я также уверен, что кто-то исправит меня, если я ошибаюсь), но Delphi не гарантирует, что это так. Это можно исправить, изменив код на:

function GimmeNewData(): PFILEDATA;
begin
  New(Result);
  ZeroMemory(Result, SizeOf(TFILEDATA));
end;

Вы всегда должны либо обнулять память, выделенную для записи, либо, по крайней мере, заполнять все поля чем-то другим, относящимся к делу. Это поведение отличается от объектов, которые гарантированно обнуляются при выделении.

Надеюсь, это поможет.

1 голос
/ 06 августа 2011

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

type
  TFileType80 = array[0..80] of WideChar;

  PFILEDATA = ^TFILEDATA;
  TFILEDATA = record
    Description80: TFileType80;
    FullPath: WideString;
    pNext: PFILEDATA;
  end;

function GimmeNewData: PFILEDATA;
begin
  New(Result);
  Result^.pNext:= nil;
end;
...