避免“变量可能未初始化”的错误

12

最近我遇到了一个类似以下代码的例程:

procedure TMyForm.DoSomething(list: TList<TMyObject>; const flag: boolean);
var
  local: integer;
begin
  if flag then
    //do something
  else local := ExpensiveFunctionCallThatCalculatesSomething;

  //do something else
  for i := 0 to list.Count do
    if flag then
      //do something
    else if list[i].IntValue > local then //WARNING HERE
        //do something else
end;

即使通过阅读代码,你可以确定除非运行初始化它的分支,否则不会碰到该行,但是程序还是会提示Variable 'local' might not have been initialized

现在,我可以通过在过程顶部添加无用的local := 0;来消除此警告,但我想知道是否有更好的结构方式来避免这个问题。有人有什么想法吗?


1
尽管通过阅读代码,您可以知道除非运行初始化它的代码分支,否则不会触发该行,但编译器并不那么聪明。这是您正在做的微妙之处,以确保仅在初始化后才访问本地变量,并且它会倾向于发出警告。 - Nick Hodges
4
有人真的应该指出循环计数器的索引超限。 ;) - Deltics
哎呀!那是我简化时犯的错误,而不是原始代码的错误。很好发现了。 - Mason Wheeler
我同意Nick的说法。我宁可有太多的警告,也不愿意漏掉一个。Mason,在你的情况下,确实你永远不可能到达那里。但是在某些情况下,你可能会错过初始化。在这种情况下,警告非常准确。关闭警告肯定会导致以后的巨大痛苦。 - Runner
由于很难找到,所以我在这里添加。抑制警告的方法(例如,在你没有编写的源代码中):**{$WARN USE_BEFORE_DEF OFF}** - Ian Boyd
显示剩余4条评论
5个回答

12

我会将其分为两个for循环 - 一个用于flag为真时,另一个用于flag为假时。额外的好处是,您不需要在每次迭代中执行if语句。


我觉得这种可能性极小,但你当然可以编写两种不同方式的程序例程,然后比较每种情况下代码的CPU反汇编视图,自行确定。无需怀疑 ;) - Deltics
是的,那是一个不错的性能优化,但我不明白它如何消除编译器警告。 - Mason Wheeler
2
因为在“如果没有标志”分支中,变量将在使用之前被初始化,在另一条流程中它将根本不被使用(或引用)。 - Deltics
@Mason,它应该消除警告,因为在flag=true分支中,您从未尝试访问local的值。试试看。 - Neil

6
将代码重构为两个不同的流程,基于标志参数进行区分:
procedure TMyForm.DoSomething(list: TList<TMyObject>; const flag: boolean);
var
  local: integer;
begin
  if flag then
  begin
    //do something
    //do something else
    for i := 0 to Pred(list.Count) do
      //do something
  end
  else
  begin
    local := ExpensiveFunctionCallThatCalculatesSomething;

    //do something else
    for i := 0 to Pred(list.Count) do
      if list[i].IntValue > local then
        //do something else
  end;
end;

这基本上重申了由neilwhitaker1给出的答案,但也明确指出了local变量的初始化应该放在条件分支内部,这就是解决编译器警告的方法(如果变量在可能未被初始化的分支中使用,则会发出警告——在根本不使用它的分支中不会发出此类警告,在使用它的分支中它肯定会被初始化,并且由于它在一个分支中被使用,您也不会得到“可能未使用”的提示)。 注意: 如果任何“// something else”在每个分支中都是相同的,当然可以将它们重构为本地嵌套过程以避免重复。 还要注意: 在上面的代码中,我已经纠正了for循环中的循环索引超限问题。 :)

2
添加备注:这两个分支看起来不太一样。也许可以将其拆分为两个函数:“如果标志为真,则执行简单操作,否则执行昂贵操作;”。 - LeGEC
这是原作者进一步重构的决定 - 如果不知道“某些东西”和“其他某些东西”是什么,就无法确定这两个分支有多相似。 :) - Deltics

6

在我看来,将0赋值给变量在这里并不是无用的——它有益于可维护性。这样做可以帮助某个人(也许是你自己未来的自己)节省一两分钟的时间来确定代码是否有效。即使设计巧妙,对他们来说也可能被忽略(即使这个人是你自己!)


2
有时候这种赋值会带来负面影响,因为它会让编译器保持沉默,但这只是掩盖了一个语义错误:比如说梅森的继承者错误地将 if 条件替换为 if not flag then。那么警告就是合理的,但是 local := 0; 将会抑制它。所以在这种情况下,我认为这不仅没有用,而且还会导致性能下降。 - Uli Gerhardt
1
我曾经看到过这样的情况,添加初始化可以消除虚假的“可能未被初始化”的警告,但却会产生另一个警告:“分配给x的值从未被使用” - 似乎编译器有时无法将两个东西联系起来。 - J...

1
我会将你的程序顶部更改为:
procedure TMyForm.DoSomething(list: TList<TMyObject>; const flag: boolean);
var
  local: integer;
begin
  if flag then
   begin
     local := 0;
     //do something
   end
  else local := ExpensiveFunctionCallThatCalculatesSomething;

等等...

这样,无论标志是什么,本地都被设置了,更重要的是,如果标志为false,则不会被设置两次。


1

添加 local:=0 是一个好的解决方案。

据我所知,提示是因为可能存在未初始化的变量。如果您始终为初始化和使用尝试最终块进行错误检查分配变量值,则不会遇到任何问题。

据我所知,编译器会给出提示,因为即使在运行时检查标志,您的变量也可能未被赋值,导致代码无法按预期运行。

对我的糟糕英语表示抱歉 :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接