在低级系统中进行适当数量的错误检查

时间:2014-11-15 02:19:53

标签: c embedded hardware

我正在接受一家测量和测试公司的低层角色面试,面试官询问这段代码是否有效:

void changeValue(int *a, int size){
  for(int i = 0; i < size; i++){
        a[i] = i;
   }

}

但显然这更为正确:

void changeValue(int *a, int size){
  if(a){
    for(int i = 0; i < size; i++){
        a[i] = i;
    }
  }
}

我知道你应该检查malloc()是否返回空指针,但这似乎非常具有防御性。我在SJSU只上了几门低级课。这种类型的堆栈错误检查在嵌入式系统中是否常见?是否有初级程序员应该知道的错误检查的“主列表”?

3 个答案:

答案 0 :(得分:3)

代码是有效的,但即使在建议的改进之后,本质上也是不安全的。

  • if (a)将捕获一个空指针,但指针可能有许多其他错误值。
  • 指针可以是悬空指针 - 指向先前发送到free()的内存的指针。
  • 没有办法判断a是否指向实际上有size个元素数的数组。

面试官更希望你提出这几个问题。

答案 1 :(得分:3)

@CAB所说的一切都是正确的,但这里有一个不同的观点:

  1. 无论什么代码直接调用malloc,都必须检查分配的有效性,除非你有一个系统范围的陷阱用于不成功的分配调用,例如你碰巧在C ++中并且有异常顶级代码中的处理程序,告诉用户事情已经非常糟糕,应用程序现在将失败。

  2. NULL(NULL指针)是一个常见且有意义的指针值。这可能是分配失败的结果(第1点),但可能代表已删除但尚未分配的对象,或者尚未更新的引用。在这种情况下,检查指针是否为非NULL是合理的,如果不是完全必要的话。经验告诉我,当你无法控制调用代码时,这种错误捕获在实践中是个好主意。主要公司的代码中仍然存在间歇性错误,因为空值有时会传递给我写的代码。

  3. 接收代码的责任是检查输入值的“有效性”,包括指针指的是实际内存。首先,大多数系统没有能力做到这一点。另一方面,如果传递非NULL无效指针,系统内部的某些内容已经非常错误,可能无法恢复。

答案 2 :(得分:1)

问题的答案&#34;是否有效&#34;显然是#34;是&#34;,如果它编译它是有效的。也许&#34;它是否安全&#34;是一个更好的问题,但C本质上是不安全的,容易被滥用和误用,导致系统失败。

检查输入参数的有效性可能并不罕见,但对于低级别的&#34; leaf&#34;系统通常最终会对相同数量的多次执行相同的有效性检查,从而无效。

此函数的问题在于,如果a为NULL,则函数将失败,但这只是一种失败模式 - 还有一些是您无法轻易防范的。调用者已经负责确保输入是有效的指针并且该大小被适当地分配,因此使用此函数检查NULL提供最小的保护,可能相当大的成本是在整个代码中使用类似的检查。

所有这一切说明,在具有不同经验和能力的多个开发人员的项目中,可能不是指针有效或先前已经检查过。此外,如果有问题的代码是要在多个项目中重用的库代码,那么这种挑剔的检查可能会很有用。

在这种情况下,建议的解决方案存在严重缺陷;它检测到一个错误,但没有做任何事情 - 它甚至没有返回问题的指示。它只是返回没有做任何事情,留下一些其他代码失败或执行相同的检查。

在这种情况下,作为一名面试官,如果您提出以下建议,我会给予更大的信任:

int changeValue(int *a, int size)
{
    assert( a != 0 ) ;

    int i = 0 ;
    if( a != 0 )
    {
        for( i = 0; i < size; i++)
        {
            a[i] = i;
        }
    }
    return i ;
}

在这种情况下,a不为NULL的前置条件由断言进行测试。标准断言宏仅在NDEBUG 定义时定义,因此检查仅在调试版本中完成,使测试的版本代码免于不必要的开销。当NDEBUG未定义时,断言将导致调用适当的处理程序;在嵌入式系统中,这通常是无限循环或强制断点,因此系统可能在调试器中被中断,并且检查调用堆栈或跟踪数据以确定故障发生的位置。

当定义NDEBUG时,断言无效,但该函数仍会返回一个值,表示其成功(成功时size失败时返回0)。您可以通过重新排列函数来避免在调试中对a进行双重测试,如下所示:

int changeValue(int *a, int size)
{
    int i = 0 ;
    if( a == 0 )
    {
        assert( false ) ;
    }
    else
    {
        for( i = 0; i < size; i++)
        {
            a[i] = i;
        }
    }

    return i ;
}

你当然应该避免假设指针只引用malloc()提供的数据 - 当然不是这种情况,间接有很多用途,并且传递的指针可能不是malloc指针,所以简单检查malloc返回是不够的。

最后还有一个需要提出的发展政策问题;你错误检查可能出现在正确代码中的条件,或者你检查实际的错误?包含正确使用检查的库代码是有效的 - 因为它将在原始开发人员可能没有设想的许多不同情况下使用和重用,但我不确定采访者的例子是一个特别令人信服的案例。另一方面,应用程序特定代码应该在任何情况下进行详尽的测试,并且检查编码错误而不是合理的系统条件是不太有用的。