【问题标题】:Saving a value between function calls in C在 C 中的函数调用之间保存一个值
【发布时间】:2017-10-25 23:28:49
【问题描述】:

这两段代码的功能有什么不同吗?主要区别是在第一个示例中使用“静态”来保存每次调用 function1 时 x 的值。但是第二个示例通过在 for 循环的每次迭代中将 i 的值从 main 传递给 function1,从而完全不需要使用“静态”。两者的输出完全相同。使用一种方式比另一种方式有什么隐藏的优势吗?

注意:第一个示例是我见过的一段代码的简化版本。只是想知道为什么使用它而不是替代方法。

第一个例子:

void function1()
{
    static int x = 0;
    printf("function1 has now been called %d times\n", ++x);
}

int main(void)
{
    for (int i = 0; i < 5; i++)
        function1();
    return 0;
}

第二个例子:

void function1(int i)
{
    printf("function1 has now been called %d times\n", ++i);
}

int main(void)
{
    int i;
    for (i = 0; i < 5; i++)
        function1(i);
    return 0;
}

如果有任何共享知识,我将不胜感激。

【问题讨论】:

  • 假设function1在代码的不同地方被调用。现在选择是局部静态变量或全局变量。你觉得哪个更好?
  • 啊,我明白他们为什么现在使用静态变量了,这在更大的程序的上下文中是明智的。谢谢!
  • 在您的测试中,运行该函数几次(例如,调用它们[复制/粘贴实际的函数调用])以查看不同的结果。这里没有“更好”,它只取决于您希望该功能实际做什么!您是否希望该函数记住它在哪里,或者您是否希望有一个可以使用输入调用的函数并将其传回一个输出,而与您调用它的次数无关?
  • 因为静态变量赋值只发生一次。在第一个代码中,在第一次调用函数时,x 被初始化为零,并且对于进一步的调用链,零增量为 1。一加一,二加一,以此类推。
  • 在第二个例子中,function1 不会修改 main 中的 i ;这两个例子做不同的事情

标签: c function static


【解决方案1】:

正如人们在 cmets 中所说,每种方法都有优点/缺点。你选择哪一个取决于你所处的情况,以及你愿意做出什么样的权衡。以下是一些帮助您滚动的想法。

静态变量方法

void function1(void)
{
    static int x = 0;
    printf("function1 has now been called %d times\n", ++x);
}

优点:

  • 较低的资源使用率:您没有在堆栈上传递x,因此如果内存是溢价的话,您使用的内存更少。此外,您保存了一些将参数移动到堆栈上的指令。地址也是固定的,所以你不需要在代码中存储或操作它。

  • 良好的局部性:作为一个静态变量,x 对其用途的范围很小,这意味着代码更易于理解和调试。如果x的范围扩大到整个文件,就更难理解了(谁在修改x,怎么改等等)。

  • 架构更灵活(简单的界面):使用此功能确实没有错误的方法 - 只需调用它即可。无需担心验证输入。如果您需要移动它,只需放入标题中即可。

缺点:

  • 功能不够灵活:如果您需要更改x 的值,例如,需要重置它,您没有办法这样做。您需要以某种方式更改您的设计(将x 设为全局,向函数添加一些重置参数等)以使其正常工作。需求一直都在变化,而能够做出最小限度的改变来做你想做的事情是好的设计的标志。

  • 更难测试:结合上面的观点,你将如何对这个函数进行单元测试?如果你想测试一些小数字和一些大数字(典型的边界测试),你需要遍历整个空间,如果函数需要很长时间运行,这可能不可行。

论证方法

void function1(int x)
{
    printf("function1 has now been called %d times\n", x);
}

void caller(int *x) /* could get x from anywhere */
{                   /* showing it as a pointer from outside here */
    *x = *x + 1;
    function1(*x);
}

优点:

  • 功能更灵活:如果您的设计要求发生变化,更改相对简单。如果您需要更改序列或有特殊条件,例如重复第一个值或跳过特定值,则可以从调用代码中轻松完成。你已经分离了一些东西,所以你可以安装一个新的驱动程序,不再需要接触这个功能。事物更加模块化。

  • 更易于测试:该功能可以更轻松地进行测试,从而更加确信它确实有效。您可以轻松进行边界测试、测试您担心的输入或重新创建故障场景。

  • 更容易理解:这种格式的函数能够更容易理解。如果一个函数对给定的一组输入产生相同的输出,则称它是纯函数。给出的例子并不纯粹,因为它在做 IO(打印到屏幕)。但是,一般来说,纯函数更容易推理,因为它不保存任何内部状态,因此您真正关心的唯一事情是输入。就像 1 + 1 = 2 一样,纯函数也具有同样的简化属性。

  • (可能)性能更高:在纯函数的情况下,编译器可以利用函数的引用透明性(一个花哨的词意味着您可以将 add(1,1) 替换为 3 ) 并安全地缓存先前调用的结果。如果你已经做过同样的工作,为什么还要再做一次?如果调用该函数的成本特别高,并且处于一个使用类似参数调用它的紧密循环中,那么您就节省了大量的循环。同样,这个函数不是纯粹的,但即使是,你也不会得到任何性能提升,因为它是一个顺序计数器。当计数器换行或再次使用相同的参数调用函数时,将看到任何好处。

缺点:

  • 更多的资源使用:如果您被挤压内存,则在传递变量时会使用更多的堆栈空间。您还可以使用更多指令来跟踪它的地址并将其移动。

  • 更容易搞砸:如果您只支持数字 1-10,那么有人会将其传递 0 或 -1。现在您需要决定如何处理。

  • (可能)性能降低:您还可以阻止代码处理情况,这些情况不应该以防御性编程的名义发生(这是一个很好的事物!)。但一般来说,防御性编程并不是为了速度而构建的。速度来自经过深思熟虑的假设。如果您保证您的输入在某个范围内,您可以让事情尽可能快地进行,而不会在您的管道中进行烦人的健全性检查。公开此接口所获得的灵活性是以性能为代价的。

  • 架构上不太灵活(更笨拙):如果你从很多地方调用这个函数,现在你需要沿着这个参数串起来以提供给它。如果你在一个特别深的调用堆栈中,可能有 20 个或更多的函数传递这个参数。如果设计发生变化并且您需要将此函数调用从一个地方移动到另一个地方,那么您很高兴从现有调用堆栈中删除参数,更改所有调用者以符合新签名,将参数插入到新的调用堆栈,并更改所有 it's 调用者以符合新签名!您的另一个选择是不理会旧的调用堆栈,这会导致更难的可维护性和更高的“嗯”因素,当有人细读过去的额外包袱时。

【讨论】:

  • “参数方法”中的代码示例实际上不起作用
  • 谢谢,我已经更改它以突出逻辑分离。前面的代码实际上可以工作(使用适当的周边逻辑),但它可能会导致更多的混乱。
猜你喜欢
  • 2017-12-14
  • 1970-01-01
  • 1970-01-01
  • 2018-05-09
  • 1970-01-01
  • 2017-10-16
  • 2015-11-16
  • 2018-06-24
  • 1970-01-01
相关资源
最近更新 更多