我正在与一位同事讨论如何在我们的代码库中构造返回代码检查。在很多地方,代码看起来像这样:
logOnError(functionWithLotsOfParameters(a, b, c, d, etc),
"Error Message", module_name, some_more_stuff);
Run Code Online (Sandbox Code Playgroud)
我认为这种隐藏了这里实际发生的事情,即内部函数的函数调用。我想像这样构造代码:
ReturnType ret;
ret = functionWithLotsOfParameters(a, b, c, d, etc);
logOnError(ret, "Error Message", module_name, some_more_stuff);
Run Code Online (Sandbox Code Playgroud)
ReturnType在这种情况下基本上只是一个unsigned int. 我的同事认为,这不必要地增加了函数的堆栈大小,这可能是一个问题,因为它在嵌入式系统上运行,而我们在某种程度上受到内存限制。
我的反驳观点是,即使在第一种情况下,返回值也必须位于内存中的某个位置,所以我认为占用空间不会增加。
谁是对的?
通常的良好做法是将复杂的表达式拆分为多个、跨几行。可读、可维护的代码几乎总是比堆栈使用甚至执行速度更重要。此外,像这样的微优化通常不值得付出努力 - 假设编译器的优化器会处理它,除非您有充分的理由不相信。
值得注意的是,如果堆栈的使用很重要,那么您不应该首先对函数使用如此繁重的 API,而应传递一个指向 struct 的指针。这实际上是一种堆栈使用优化技术,有时用于一些非常有限的系统,如低端 8 位微控制器。例如,PIC 因其功能失调的堆栈实现而臭名昭著,除了大小之外,您还必须跟踪调用堆栈深度。
您假设返回的值不能凭空存储是正确的 - 程序使用的每个值都必须分配到某个地方,无论存储在命名变量还是匿名临时位置。但不一定在堆栈上,也可以在寄存器内,具体取决于 ABI 和调用约定。
在更加人工的环境中测试您的人工代码https://godbolt.org/z/bYsE1WT7e ...
为什么第一个版本在某些系统上速度较慢是因为 ABI 以及特定编译器如何根据 ABI 进行优化。ABI 规定返回代码必须存储在哪里以及第一个参数必须存储在哪里:不同的地方。这反过来可能意味着编译器必须在函数调用之间稍微调整数据。
吸取的教训是,过度思考/过度设计各种微优化以及为了性能而尝试编写可读性较差的代码可能会产生相反的效果。一般的最佳实践是:
例如,我正要自信地说这些片段肯定会得到相同的优化,但后来发现它们并不针对某些目标。