Ответ 1
Позвольте мне рассказать вам, как этот код выходит из строя и как компилятор позволяет вам стрелять в ногу.
Если вы пройдете через код с помощью отладчика, вы увидите, что произойдет.
После инициализации Thing1
вы можете видеть, что FData
заполняется всеми нулями.
Как ни странно, Thing2
отлично.
Поэтому ошибка находится в CreateThing
. Пусть далее исследуют...
В нечетно названном конструкторе CreateThing
у вас есть следующая строка:
FData := @FBuf[1];
Это выглядит как простое назначение, но на самом деле это вызов DynArrayAssign
Project97.dpr.32: FData := @FBuf[1];
0042373A 8B45FC mov eax,[ebp-$04]
0042373D 83C008 add eax,$08
00423743 8B5204 mov edx,[edx+$04]
00423746 42 inc edx
00423747 8B0DE03C4000 mov ecx,[$00403ce0]
0042374D E8E66DFEFF call @DynArrayAsg <<-- lots of stuff happening here.
Одна из проверок DynArrayAsg
выполняется, чтобы проверить, пуст ли исходный динамический массив или нет. DynArrayAsg
также делает несколько других вещей, о которых вам нужно знать.
Сначала рассмотрим структуру динамического массива; это не просто простой указатель на массив!
Offset 32/64 | Contents
--------------+--------------------------------------------------------------
-8/-12 | 32 bit reference count
-4/-8 | 32 or 64 bit length indicator
0/ 0 | data of the array.
Выполняя FData = @FBuf[1]
, вы испортили префиксные поля динамического массива.
4 байта перед @Fbuf[1]
интерпретируются как длина.
Для Thing1 это:
-8 (refcnt) -4 (len) 0 (data)
FBuf: 01 00 00 00 08 00 00 00 00 'S' 'n' .....
FData: 00 00 00 08 00 00 00 00 .............. //Hey that a zero length.
К сожалению, когда DynArrayAsg
начинает расследование, он видит, что то, что, по его мнению, является источником для назначения, имеет длину, равную нулю, то есть полагает, что источник пуст и ничего не назначает. Он оставляет FData
неизменным!
Работает ли Thing2
по назначению?
Похоже, это так, но на самом деле это не так плохо, позвольте мне показать вам.
Вы успешно обманули среду выполнения, полагая, что @Fbuf[1]
является действительной ссылкой на динамический массив.
Из-за этого указатель FData
был обновлен, чтобы указать на FBuf[1]
(пока это так хорошо), а счетчик ссылок FData был увеличен на 1 (не очень хорошо), а также время выполнения увеличило блок памяти, динамический массив соответствует тому, что он считает правильным размером для FData
(плохой).
-8 (refcnt) -4 (len) 0 (data)
FBuf: 01 01 00 00 13 00 00 00 01 'S' 'n' .....
FData: 01 00 00 13 00 00 00 01 'S' ..............
Упс FData
теперь имеет коэффициент пересчета 318 767 105 и длину 16 777 216 байт. FBuf
также имеет увеличенную длину, но его refcount теперь составляет 257.
Вот почему вам нужен вызов SetLength
, чтобы отменить массовое перекрытие памяти. Это все еще не фиксирует количество ссылок.
Общееобнаружение может привести к ошибкам в памяти (особенно на 64-битной основе), а причудливые пересчеты вызовут утечку памяти, потому что ваши массивы никогда не будут освобождены.
Решение
В соответствии с ответом Дэвида: включите типизированные проверенные указатели: {$TYPEDADDRESS ON}
Вы можете исправить код, указав FData
как обычный PAnsiChar
или PByte
.
Если вы всегда выполняете свои назначения до FBuf
с двойным нулем, FData будет работать как ожидалось.
Сделайте FData
a TBuffer
следующим образом:
TBuffer = record
private
FData : PByte;
function GetLength: cardinal;
function GetType: byte;
public
class operator implicit(const A: TBytes): TBuffer;
class operator implicit(const A: TBuffer): PByte;
property Length: cardinal read GetLength;
property DataType: byte read GetType;
end;
Перепишите CreateThing
так:
constructor TThing.CreateThing(const AThingType : Byte; const AThingData: TBytes);
begin
SetLength(FBuf, Length(AThingData) + Sizeof(AThingType) + 2);
FBuf[0] := AThingType;
Move(AThingData[0], FBuf[1], Length(AThingData));
FBuf[Lengh(FBuf)-1]:= 0;
FBuf[Lengh(FBuf)-2]:= 0; //trailing zeros for compatibility with pansichar
FData := FBuf; //will call the implicit class operator.
end;
class operator TBuffer.implicit(const A: TBytes): TBuffer;
begin
Result.FData:= PByte(@A[1]);
end;
Я не понимаю все это, когда вы пытаетесь перехитрить компилятор.
Почему бы просто не объявить FData так:
type
TMyData = record
DataType: byte;
Buffer: Ansistring;
....
И работайте с этим.