'Value never used' 更改为 'variable not initialized'

'Value never used' changes to 'variable not initialized'

如果我编译下面的代码,它会在第 1 行发出警告:

value assigned to lTime never used*

但是如果我删除该行,我会在第 2 行收到不同的警告:

variable lTime might not have been initialized

是编译器遗漏了什么,还是我遗漏了什么?

procedure TFormWebServices.RemoveOldReports;
var
  TSR       : TSearchRec;
  I         : Integer;
  lCutOff,
  lTime     : Int64;
  TSToDelete: TStringList;
  S,Msg     : String;
  E         : Exception;
begin
  lCutOff := DelphiToJavaDateTime(Now - cDefReportLifeMins/1440);
  I := FindFirst(FReportDir + '*.pdf',0,TSR);
  TSToDelete := TStringList.Create;
  while I = 0 do
  begin
    if (TSR.Attr and faDirectory) = 0 then 
    begin
      lTime := lCutOff;          // Line 1
      try
        lTime := StrToInt64(Copy(TSR.Name,1,pos('.',TSR.Name)-1));
      except
        on E:Exception do lTime := lCutOff;
      end;
      if lTime < lCutOff then    // Line 2
        TSToDelete.Add(TSR.Name);
    end;
    I := FindNext(TSR);
  end;

这不是对 Why is the Compiler warning that variable may not be initialized? 的欺骗,因为我也在异常中分配了 lTime

这是正确的行为。第 1 行的值永远不会被使用,因为在 try/except 中你分配了一个新值而不使用前一个值,因此出现警告。

如果您删除了您尝试在 try/except 块之外使用 lTime 变量的行,这可能会失败而实际上没有为 lTime

设置值
procedure TFormWebServices.RemoveOldReports;
var
  TSR       : TSearchRec;
  I         : Integer;
  lCutOff,
  lTime     : Int64;
  TSToDelete: TStringList;
  S,Msg     : String;
  E         : Exception;
   begin
   lCutOff := DelphiToJavaDateTime(Now - cDefReportLifeMins/1440);
   I := FindFirst(FReportDir + '*.pdf',0,TSR);
   TSToDelete := TStringList.Create;
   while I = 0 do
      begin
         if (TSR.Attr and faDirectory) = 0 then 
            begin
               lTime := lCutOff;          // Line 1
               try
                  lTime :=     StrToInt64(Copy(TSR.Name,1,pos('.',TSR.Name)-1));
               except
                  on E:Exception do lTime := lCutOff;
               end;
               if lTime < lCutOff then    // Line 2
                  TSToDelete.Add(TSR.Name);
            end;
         I := FindNext(TSR);
      end;

你可以这样做

procedure TFormWebServices.RemoveOldReports;
var
  TSR       : TSearchRec;
  I         : Integer;
  lCutOff,
  lTime     : Int64;
  TSToDelete: TStringList;
  S,Msg     : String;
  E         : Exception;
   begin
   lCutOff := DelphiToJavaDateTime(Now - cDefReportLifeMins/1440);
   I := FindFirst(FReportDir + '*.pdf',0,TSR);
   TSToDelete := TStringList.Create;
   while I = 0 do
      begin
         if (TSR.Attr and faDirectory) = 0 then 
            begin
               lTime := StrToInt64Def(Copy(TSR.Name,1,pos('.',TSR.Name)-1), lCutOff);
               if lTime < lCutOff then    
                  TSToDelete.Add(TSR.Name);
            end;
         I := FindNext(TSR);
      end;
lTime := lCutOff;       
try
  lTime := StrToInt64(Copy(TSR.Name,1,pos('.',TSR.Name)-1));
except
  on E:Exception do lTime := lCutOff;
end;
if lTime < lCutOff then
  TSToDelete.Add(TSR.Name);

编译器对这段代码发出警告是正确的。当您在第一行分配 lTime := lCutOff 时,永远不会读取该分配中写入的值。

但是,当您删除代码时,事情就不那么清晰了。

try
  lTime := StrToInt64(Copy(TSR.Name,1,pos('.',TSR.Name)-1));
except
  on E:Exception do lTime := lCutOff;
end;
if lTime < lCutOff then
  TSToDelete.Add(TSR.Name);

有两种情况需要考虑:在 try/except 块内没有引发异常,或者在那里引发了异常。

如果没有引发异常则很简单,lTime 被赋值为 StrToInt64 的结果,因此在读取之前被初始化。

在引发异常时,lTime 不会在块内初始化。接下来会发生什么?

  • 如果异常源自 Exception(非强制性),则它会被捕获并初始化 lTime
  • 如果异常不是从 Exception 派生的,那么它不会被捕获并且永远不会读取 lTime

因此,编译器可以推断出所有这些,因此不会发出未初始化的变量警告。但是,遗憾的是,编译器不会对这种复杂性进行流分析。我相信它的逻辑是这样运行的:

  1. lTime初始化前抛出异常,然后
  2. 可以捕获到异常,在这种情况下
  3. 然后读取变量 lTime,但仍可能未初始化。

在第 2 步中,它应该能够意识到 lTime 已初始化,但它根本不执行此类分析。因此,尽管有人可能会争辩说编译器可以做得更好,但您只需要接受这是其分析算法的局限性。

了解到我们剩下的任务是找到一种方法来编写代码并避免警告。我们不想抑制警告,我们只需要找到一种方法来编写代码,使其既正确又没有警告。

在我看来,前进的方向是认识到异常是在这里使用的错误工具。此转换失败是正常行为。您应该使用在失败时不会引发异常的转换函数来编写代码。例如,您可以使用以下选项之一:

if TryStrToInt64(..., lTime) then
  if lTime < lCutOff then
    ....
else
  lTime := lCutoff;

或:

lTime := StrToInt64Def(..., lCutoff);
if lTime < lCutOff then
  ....