Ошибка нарушения доступа Delphi при назначении строк между типами записей - PullRequest
5 голосов
/ 26 февраля 2011

У меня простой тип записи.Я выделяю новый экземпляр этой записи и использую процедуру ("_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;

Ответы [ 2 ]

20 голосов
/ 26 февраля 2011

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

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

после GetMem, прежде чем использовать запись.

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

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

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

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

New(newItem);

Компилятор выведет размер выделения из типа указателя (sizeof, на который указывает тип указателя), выделите память и инициализируйте все поля, подходящие для вас.

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

Dispose(newItem);

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

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

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

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

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

2 голосов
/ 26 февраля 2011

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

AccessoryItem1 := AccessoryItem2;

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

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

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

...