Инициализировать результат строковой функции? - PullRequest
27 голосов
/ 15 июля 2010

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

function TMyObject.GenerateInfo: string;

        procedure AppendInfo(const AppendStr: string);
        begin
          if(Result > '') then
            Result := Result + #13;
          Result := Result + AppendStr;
        end;

begin
  if(ACondition) then
    AppendInfo('Some Text');
end;

Вызов этогоФункция несколько раз привела к:

"Some Text"

в первый раз,

"Some Text"
"Some Text"

во второй раз,

"Some Text"
"Some Text"
"Some Text"

в третий раз и т. д.

Чтобы исправить это, я должен был инициализировать Результат:

begin
  Result := '';
  if(ACondition) then
    AppendInfo('Some Text');
end;

Нужно ли инициализировать результат строковой функции?Почему (технически)?Почему компилятор не выдает предупреждение «W1035 Возвращаемое значение функции« xxx »может быть неопределенным» для строковых функций?Нужно ли мне проходить весь мой код, чтобы убедиться, что значение установлено, так как ненадежно ожидать пустую строку от функции, если результат не установлен явно?

Я проверял это вновое тестовое приложение и результат тот же.

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  S: string;
begin
  for i := 1 to 5 do
    S := GenerateInfo;
  ShowMessage(S); // 5 lines!
end;

Ответы [ 7 ]

32 голосов
/ 15 июля 2010

Это не ошибка, но " feature ":

Для строки, динамического массива, указателя метода или варианта результата эффекты такие же, как если результат функции был объявлен как дополнительный параметр var после объявленных параметров.Другими словами, вызывающая сторона передает дополнительный 32-битный указатель, который указывает на переменную, в которой нужно вернуть результат функции.

Т.е. ваш

function TMyObject.GenerateInfo: string;

действительно такой:

procedure TMyObject.GenerateInfo(var Result: string);

Обратите внимание на префикс « var » (а не « out », как вы можете ожидать!).

Это ТАКОЕ не интуитивно понятно, поэтому приводит к всевозможным проблемам в коде.Код, о котором идет речь, - только один пример результатов этой функции.

Смотрите и голосуйте за этот запрос .

4 голосов
/ 15 июля 2010

Мы сталкивались с этим раньше, я думаю, может быть, еще в Delphi 6 или 7. Да, хотя компилятор не удосужился предупредить вас, вам нужно инициализировать строковые переменные результата, дляименно поэтому вы столкнулись.Строковая переменная инициализируется - она ​​не запускается как ссылка на мусор - но, похоже, она не инициализируется re , когда вы этого ожидаете.

Что касается того, почему это происходит ... не уверен.Это ошибка, поэтому ей не обязательно нужна причина.Мы видели это только тогда, когда неоднократно вызывали функцию в цикле;если мы назвали это вне цикла, это работало как ожидалось.Похоже, что вызывающая сторона выделяет пространство для переменной Result (и повторно использует ее, когда она неоднократно вызывала одну и ту же функцию, что приводит к ошибке), а не функция , выделяющая свою собственную строку(и выделение нового при каждом вызове).

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

2 голосов
/ 15 июля 2010

Это не ошибка. По определению переменная внутри функции не инициализируется, включая Result.

Таким образом, ваш Результат не определен при первом вызове и может содержать что угодно. Как это реализовано в компиляторе, не имеет значения, и вы можете получить разные результаты в разных компиляторах.

1 голос
/ 15 июля 2010

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

function TMyObject.GenerateInfo: string;
begin
  if(ACondition) then
    Result := 'Some Text'
  else
    Result := '';
end;

Обычно вы не хотите использовать Результат с правой стороны назначения в функции.

В любом случае, строго в иллюстративных целях, вы также можете сделать это, хотя и не рекомендуется:

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  S: string;
begin
  for i := 1 to 5 do
  begin
    S := ''; // Clear before you call
    S := GenerateInfo;
  end;
  ShowMessage(S); // 5 lines!
end;
0 голосов
/ 31 марта 2017

Ответ Алекса почти всегда прав, и он отвечает, почему я видел странное поведение, которым я был, но это не вся история.

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

Кажется, что есть другое правило для фактического программного блока.

По общему признанию, это угловой случай.

program Project1;

{$APPTYPE CONSOLE}

uses System.SysUtils;

  function PointlessFunction: string;
  begin
  end;

  procedure PointlessProcedure(var AString: string);
  begin
  end;

var
  sTemp: string;
begin
  sTemp := '1234';
  sTemp := PointlessFunction;
  //PointlessProcedure(sTemp);
  WriteLn('Result:' + sTemp);
  ReadLn;
end.
0 голосов
/ 15 июля 2010

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

Во многих местах есть строки, переданные по ссылке, переданные по значению, но все эти строки ожидают VALID строк, в которых счетчик управления памятью является допустимым, а не значением мусора.Таким образом, для того, чтобы строки оставались действительными, единственное, что нужно знать, это то, что они должны быть инициализированы при первом введении.Например, для любой строки локальной переменной это необходимо, поскольку именно здесь вводится строка.Любое другое использование строки, включая function (): string (которая фактически является процедурой (var Result: string), как правильно указал Александр), просто ожидает допустимых строк в стеке, а не initialized .И достоверность здесь проистекает из того факта, что конструкция (var Result: string) говорит, что «я жду допустимой переменной, которая определенно была введена ранее».ОБНОВЛЕНИЕ: Из-за этого фактическое содержимое Result является неожиданным, но из-за той же логики, если это единственный вызов этой функции с локальной переменной слева, пустота строки в этом случае гарантируется.

0 голосов
/ 15 июля 2010

Это похоже на ошибку в D2007. Я только что проверил это в Delphi 2010 и получил ожидаемое поведение. (1 строка вместо 5.)

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