菜鸟警报:
出于学习目的,我被赋予了重新实现 strlen() 函数的任务,我得到了这样的想法:最好使用宏等函数而不是函数来完成此操作, 我的理由是,使用宏我不必处理将字符串传递给函数的问题。
你有什么想法? 在这种情况下,创建适当的函数还是宏更好?
最佳答案
在编译程序时,宏只会展开一次。由于时间旅行不是 C 语言的一部分,因此程序的 future 执行不可能追溯更改宏的结果。因此,如果计算(例如计算字符串的长度)依赖于程序编译时未知的信息,则宏完全没有用处。除非字符串恰好是文字,否则就会出现这种情况。而且我敢断言,绝大多数情况下,所需长度的字符串在程序编译时并不存在。
清楚地了解宏的实际作用(在编译之前修改程序文本)将有助于避免诸如本问题中的建议之类的干扰。
在常量字符串文字上使用 strlen
有时会很有用,以避免将来修改字符串文字时可能引入的错误。例如,以下内容(测试 line
是否以文本 Hello
开头):
/* Code smell: magic number */
if (strncmp(line, "Hello", 5) == 0) { ... }
最好写成:
/* Code smell: redundant repetition, see below */
if (strncmp(line, "Hello", strlen("Hello")) == 0) { ... }
显然,如果可以在编译时执行一次计算,那么最好这样做,而不是在程序运行时重复执行。曾几何时,编译器还很原始,几乎无法理解控制流,担心这些事情是有道理的,尽管即使如此,许多手动优化对于所实现的微小好处来说也过于复杂。
如今,对于不成熟的优化器来说,甚至这个借口也无法使用。大多数现代 C 编译器都能够完美地用常量 5 替换 strlen("Hello");
,这样库函数就不会被调用。实现这种优化不需要任何宏观魔法。
如前所述,示例中的测试仍然存在不必要的前缀字符串重复。我们真正想写的是:
if (startsWith(line, "Hello")) { ... }
这里将 startsWith
定义为宏的诱惑非常大,因为看起来简单的编译时替换就足够了。应避免这种诱惑。现代编译器还能够“内联”函数调用;也就是说,将调用正文直接插入到代码中。
所以定义:
static int startsWith(const char* line, const char* prefix) {
return strncmp(line, prefix, strlen(prefix)) == 0;
}
将与其等效的宏一样快,并且与宏不同的是,当使用具有副作用的第二个参数调用它时,它不会导致问题:
/* Bad style but people do it */
if (startsWith(line, prefixes[++i])) { doAction(i); }
内联调用后,编译器就可以继续应用其他优化,例如在前缀参数是字符串文字的情况下消除对 strlen
的调用。
关于c - 重新实现 strlen (C),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50656792/