静态与非静态函数 - 调试嵌入式系统上下文

18 浏览
0 Comments

静态与非静态函数 - 调试嵌入式系统上下文

我曾对以下问题感到困惑:如何保留“静态”标签的优势,同时仍然能够在现场调试生产代码?

经常会出现在客户现场发生意外行为,且只有在那里才发生。在许多情况下,能够进行调试可以节省很多工作,并提供非常快速的响应。这样的调试通常涉及检查函数行为,这就引出了“静态”定义。

静态函数无法从调试shell中进行调试,例如设置断点或执行它。

另一方面,将所有函数定义为公共的会导致代码结构和优化的困扰。

我知道有一些选项,比如至少编译两个不同的版本,一个带有静态的,一个没有静态的,但这更适用于自动化测试,而不是最终的生产版本。

非常希望从您那里得到一些见解,主要是关于您如何解决(如果有的话)这个困境。或者重新提出问题:“什么更重要?

关于C语言中的“静态”有一个很好的讨论,请点击这里

0
0 Comments

在嵌入式系统上下文中调试时,静态函数和非静态函数之间的区别成为了一个问题。这个问题的出现是因为在嵌入式代码中,如果你在Debug版本上进行大部分的测试,然后使用不同选项编译的Release版本进行发布,实际上就是向客户提供了未经测试的代码。当你在靠近硬件的层次上运行时,优化器可能会轻易引入时间或内存访问模式的小变化,这可能导致系统行为的大变化。

我目前的策略是使用配置了尽可能多优化的Debug版本进行发布。不使用静态函数,尽可能让调试器看到更多的状态。

是的,这样可能会放弃一些编译器生成的效率,但只要我确保Debug版本足够快以满足其要求,这不是一个问题。而且回报是,我发布的代码与我整个发布周期中一直在进行测试的代码完全相同,没有优化器产生的意外。

值得一提的是,“static”使用不仅与效率有关,它在很多方面是面向对象的概念(比如声明和实现适当的分离,这样其他编码人员就不会绕过定义的API)。你提出的新问题值得在这里单独讨论,有几个要考虑的利弊。

在封装和可见性方面,将函数从相应的头文件中省略几乎和声明为静态一样好。命名也可以起到帮助作用。在代码审查中问“为什么在Module2.c中调用module1DoSomething_private()?”是一个很好的问题。

文章整理如下:

我认为根本问题不是“是否使用'static'进行发布”,而是“你是否测试你所发布的代码?”对于嵌入式代码来说,如果你在Debug版本上进行大部分的测试,然后使用不同选项编译的Release版本进行发布,实际上就是向客户提供了未经测试的代码。当你在靠近硬件的层次上运行时,优化器可能会轻易引入时间或内存访问模式的小变化,这可能导致系统行为的大变化。

我的当前策略是使用配置了尽可能多优化的Debug版本进行发布。不使用静态函数,尽可能让调试器看到更多的状态。

是的,这样可能会放弃一些编译器生成的效率,但只要我确保Debug版本足够快以满足其要求,这不是一个问题。而且回报是,我发布的代码与我整个发布周期中一直在进行测试的代码完全相同,没有优化器产生的意外。

值得一提的是,“static”使用不仅与效率有关,它在很多方面是面向对象的概念(比如声明和实现适当的分离,这样其他编码人员就不会绕过定义的API)。你提出的新问题值得在这里单独讨论,有几个要考虑的利弊。

在封装和可见性方面,将函数从相应的头文件中省略几乎和声明为静态一样好。命名也可以起到帮助作用。在代码审查中问“为什么在Module2.c中调用module1DoSomething_private()?”是一个很好的问题。

0
0 Comments

在嵌入式系统中调试静态和非静态函数时遇到的问题是,无法直接在调用静态函数的公共可访问函数中设置断点,因此无法检查传入的参数和返回值。如果问题只在客户现场出现,并且单元测试/集成测试没有显示出预期输入的问题,那么问题可能是这些函数接收到了未预料到的输入(或者如果函数以某种方式具有状态,那么是输入序列),这意味着实际问题可能在您正在查看的静态函数之外。

如果您已经怀疑知道哪个静态函数包含问题,并且知道哪种类型的输入导致问题,那么您可以在发布构建中添加一个简单的单元测试函数,只需检查您怀疑的错误。当然,如果此函数直接控制例如六吨起重机之类的设备,这会变得复杂,此时您可能需要编写两个版本的函数,一个带有起重机的模拟版本,并在上面运行测试。

最不可能但并非不可能的是,不要排除发布版本和调试版本之间存在编译器不一致的情况。我们都喜欢认为编译器是不可靠的,包括我在内,但事情总会发生。而且我看到您对torek的回答中提到,给客户一个调试版本有时可以解决问题...

调试模块的API并不总是足够的,特别是当您已经怀疑私有函数中存在问题时。我完全同意您关于进行必要测试的观点,但在嵌入式环境中,这并不总是适用。很多时候我们谈论的是事件的顺序和异步处理,这可能无法通过单元测试来覆盖。

0
0 Comments

在嵌入式系统调试的背景下,出现了一个问题:静态函数与非静态函数之间的调试。有些调试器可以调试“静态”函数,但是有时静态函数在调用点会被内联展开,这使得调试器的工作变得困难,有些调试器甚至放弃了调试。内联展开并不是“静态”函数特有的属性,只是编译器更有可能这样做,因为编译器对函数“了解得更多”——具体来说,它知道该函数的名称在当前的翻译单元之外是不可见的,因此如果所有调用都被内联展开,该函数的代码可以完全省略。

以前常常使用宏来解决这个问题:

#ifndef STATIC
#  define STATIC static
#endif
...
STATIC void somefunc() { ... }

然后在调试构建时将宏变为空值。这种方法效果还不错,但是如果能找到一个聪明到能够处理静态函数的调试器,即使在内联展开的情况下,那就更好了。

这个宏解决了内部测试期间的问题,但是当问题出现在客户现场时,它就不太适用了。有时,放置一个调试版本可以消除问题。

0