就像在 linux 内核源代码中一样,我发现很多函数都是用内联形式实现的,还有很多其他的函数也是用 MACRO 实现的。
所以我想知道在决定选择哪一个时所涉及的考虑是什么,因为我认为两者都是合适的。或者这只是一种个人风格?
就像在 linux 内核源代码中一样,我发现很多函数都是用内联形式实现的,还有很多其他的函数也是用 MACRO 实现的。
所以我想知道在决定选择哪一个时所涉及的考虑是什么,因为我认为两者都是合适的。或者这只是一种个人风格?
宏不进行类型检查并且需要其他特殊注意事项(即只对表达式求值一次),因此应尽可能避免使用它们。但是,不检查类型的优点是允许您在 C89 中编写泛型函数。
编写真正的inline
函数还允许(使用可以在内核配置中激活的某个标志)编译器也可以取消内联它,如果这具有性能优势,而这对于宏来说是不可能的,因为它是一个简单的文本替换。
作为文本替换的宏可以做的不仅仅是内联函数。它们也可能有不必要的副作用。考虑众所周知的交换宏。
#define SWAP(a,b) {int t=a; a=b; b=t}
并注意到SWAP(i,t[i])
做不想要的事情。
内联函数具有类似语义的函数调用的优点(参数被评估一次,等等......)。
所以这主要是编码风格的问题,以及认识到宏和函数是不同种类的动物。
而且您的问题与内核并没有真正相关。任何 C(甚至 C++)程序都有同样的问题。
请记住,Linux 内核中的许多代码已经存在了很长一段时间。一些宏还对输入/环境的其他部分做“聪明的事情”,而不是函数会/可以/应该做的事情。
作为一般规则,尽可能使用(内联)函数,因为函数的“陷阱”较少,如上所述。
如果您进行巧妙的替换或其他一些“只能在宏中执行”,那么如果可能的话,最好制作一个形成函数参数的宏。例如
#define ASSERT(x) do { if (!(x)) { fprintf(stderr, "Assertion failed on %s:%d", __FILE__, __LINE__); exit(1); } while(0);
可以这样做:
#define ASSERT(x) do_assert(!!(x), __FILE__, __LINE__);
void do_assert(bool val, const char *file, int line)
{
if (!val)
{
fprintf(....);
exit(1);
}
}
例如,现在您可以在调试构建中,在 if 内的断言中设置断点,并且每当您的断言失败时,您都可以看到您是如何到达那里的。使用宏,这是不可能的。
如果有一半的机会,现代编译器在适当的时候内联事物方面做得非常好。