Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Delphi: Общие вопросы > Баг компилятора или (пример)?


Автор: sptim 24.3.2016, 21:15
Добрый день!
Обращаюсь к знатокам, сам пишу на Делфи только в рамках сопровождения старых проектов и тут намедни поймал очень удививший меня баг. Ниже я специально набросал микро-тест воспроизводящий эту ситуацию:
Код

unit Test1;

interface

type
  PTest1 = ^TTest1;
  TTest1 = class
    fpos: integer;

    constructor Create();
    function pos(n: integer): PTest1;
    function putAt(n: integer; v: int64): PTest1;
    function put(v: int64): PTest1;
  end;

implementation

var
  t: TTest1;

constructor TTest1.Create();
begin
  fpos := 0;
end;

function TTest1.pos(n: integer): PTest1;
begin
  fpos := n;
  Result := @self;
end;

function TTest1.putAt(n: integer; v: int64): PTest1;
begin
  pos(n);
  Result := @self;
end;

function TTest1.put(v: int64): PTest1;
begin
  Result := putAt(fpos, v); // Result верный.
  pos(fpos+4); // После вызова этой ф-ции Result переопределяется "левым" значением (!!!)
end;

begin
  t := TTest1.Create();
  t.put(5).put(5); 
  // Первый вызов put возвращает "левый" указатель(!), соответственно
  // выбрасывается исключение при попытке вызова второго put
end.

Параметры среды: Delphi7, оптимизации отключены, WinXP sp2 32bit.
Причем, если заменить Int64 на Integer - ошибок не будет (!). Т.е. это какой-то косяк компилятора (работа со стеком при Int64 аргументах)? Пробовал в инете поискать - ничего не нагуглилось.
Может я чего-то недопонимаю - тогда буду благодарен за объяснение. 

Автор: dnek 25.3.2016, 10:27
Не знаю поможет или нет, но правильнее было бы написать
Код

t.put(5)^.put(5);


Этот ответ добавлен с нового Винграда - http://ru.vingrad.com/Bag-kompilyatora-ili-(primer)-id56f42f5aae201598208b4567#findElement_E7045_56f4e857ae2015cb77cd5e86_0

Автор: kami 25.3.2016, 18:30
Переменная, содержащая экземпляр класса - это уже указатель.
Поэтому объявление 
Цитата(sptim @  24.3.2016,  21:15 Найти цитируемый пост)
PTest1 = ^TTest1;

абсолютно излишне. Таким образом вы декларируете "указатель на указатель".
Сам Result в вашем коде содержит валидный указатель на область памяти, в которой должен содержаться указатель на экземпляр класса. Вот только компилятор не знает о том, что участок памяти, на который ссылается ваш указатель, нельзя модифицировать.  Чтобы сказать ему об этом - необходимо явно зарезервировать область памяти, т.е. сделать
Код

New(Result);

В противном случае возможны вот такие неочевидные казусы. При этом нельзя забывать правило: если что-то выделил/создал - не забудь его удалить! (антипод New - процедура Dispose).

Повторюсь - в вашем случае объявление PTest1 излишне. Так стоит поступать при динамическом создании record-ов, но никак не экземпляров классов.
Преобразуйте свой код так, чтобы он возвращал себя же (компилятор нормально переваривает указание типа класса внутри самого класса):
Код

function TTest1.pos(n: integer): TTest1;
begin
  fpos := n;
  Result := Self;
end;

И не забудьте про высвобождение ресурсов:
Код

begin
  t := TTest1.Create();
  try
    t.put(5).put(5); 
  finally
    t.Free;
  end;
end.

Автор: dnek 26.3.2016, 11:51
Проблема здесь заключается не в PTest1 и не в Result (с ним как раз все в порядке и выделять память под него не надо). В данном примере проблема в строке
Код

  Result := @self;

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

Этот ответ добавлен с нового Винграда - http://ru.vingrad.com/Bag-kompilyatora-ili-(primer)-id56f42f5aae201598208b4567#findElement_E7045_56f64dacae2015e04ccd61e1_0

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)