Ошибка нарушения прав доступа Delphi при назначении строк между типами записей

У меня простой тип записи. Я выделяю новый экземпляр этой записи и использую процедуру ("_clone") для копирования значений из существующей записи в новую. Я получаю нарушение прав доступа только при назначении строкового значения.

Любые идеи? Помощь очень ценится.


ОПРЕДЕЛЕНИЕ ТИПА:

TPointer = ^TAccessoryItem;
TAccessoryItem = Record
  Id : Integer;
  PartNumber : String;
  Qty : Integer;
  Description : String;
  Previous : Pointer;
  Next : Pointer;
end;

Procedure TAccessoryList._clone (Var copy : TAccessoryItem; Var original : TAccessoryItem);

 begin

    copy.Id := original.Id;
    copy.Qty := original.Qty;
    copy.Partnumber := original.Partnumber;  **// Access errors happens here**
    copy.Next := Nil;
    copy.Previous := Nil;

  end;

Вызов приложения ниже:

  procedure TAccessoryList.AddItem(Var Item : TAccessoryItem);

 Var

    newItem : ptrAccessoryItem;

 begin

    GetMem(newItem, sizeOf(TAccessoryItem));

    _clone(newItem^, Item);

 end;

person mad moe    schedule 26.02.2011    source источник


Ответы (2)


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

FillChar(newItem^, sizeof(TAccessoryItem), 0)

после GetMem перед использованием записи.

И вот почему: когда вы присваиваете значение строковому полю только что выделенной, но неинициализированной записи, RTL видит, что поле целевой строки не является нулевым (содержит указатель мусора) и пытается разыменовать строку, чтобы уменьшить ее счетчик ссылок перед назначением нового строковое значение в поле. Это необходимо при каждом присвоении строковому полю или переменной, так что предыдущее строковое значение будет освобождено, если оно больше ничем не используется до того, как новое значение будет присвоено строковому полю или переменной.

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

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

Лучший способ разместить записи в Delphi — использовать функцию New():

New(newItem);

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

Соответствующим деблокатором является функция Dispose():

Dispose(newItem);

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

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

Типы, управляемые компилятором, включают длинные строки ("String", а не "string[10]"), широкие строки, варианты, интерфейсы и, возможно, что-то, что я забыл.

Один из способов представить это так: GetMem/FreeMem просто выделяют и освобождают блоки памяти. Они ничего не знают о том, как будет использоваться этот блок. New и Dispose, тем не менее, «осведомлены» о типе, для которого вы выделяете или освобождаете память, поэтому они будут выполнять любую дополнительную работу, чтобы убедиться, что все внутренние операции выполняются автоматически.

В общем, лучше избегать GetMem/FreeMem, если только вам действительно не нужен необработанный блок памяти без связанной с ним семантики типов.

person dthorpe    schedule 26.02.2011
comment
Если по какой-либо причине вы не хотите использовать New(), используйте AllocMem() delphi.wikia.com /wiki/AllocMem_Routine, это не что иное, как ярлык для GetMem(), за которым следует FillChar(). - person arthurprs; 27.02.2011
comment
Я бы рекомендовал Initialize вместо FillChar — необработанный блок памяти без связанной с ним семантики типов. И то, что вы забыли, это динамические массивы. - person Rob Kennedy; 27.02.2011
comment
Приятно видеть, что ты отвечаешь на вопросы Delphi, Дэнни! - person Nick Hodges; 27.02.2011

Также записи являются типами значений (в отличие от ссылочных типов, таких как tObject), поэтому вы можете просто сделать

AccessoryItem1 := AccessoryItem2;

(или AccessoryItem1^ := AccessoryItem2^, если они являются указателями)

и он скопирует все поля для вас.

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

person Frank Wallis    schedule 26.02.2011
comment
В Delphi лучше передавать как var или const, чем явно использовать указатели. - person Gerry Coll; 27.02.2011