ANSI C中的#define与函数有何区别?

4

我有一个关于代码性能的问题。假设我在C语言中有一个用于表示点的结构体:

typedef struct _CPoint
{
    float x, y;
} CPoint;

我有一个使用结构体的函数,如下:

float distance(CPoint p1, CPoint p2)
{
    return sqrt(pow((p2.x-p1.x),2)+pow((p2.y-p1.y),2));
}

我在想,将这个函数替换为 #define 是否明智。
#define distance(p1, p2)(sqrt(pow((p2.x-p1.x),2)+pow((p2.y-p1.y),2)));

我认为使用宏定义替换函数可以减少函数调用的开销,从而提高程序的运行速度。因此,我想知道是否应该在我的程序中将所有函数都替换成宏定义来提高性能。

那么我的问题是:

是否应该用#define替换所有函数以增加代码的性能?


请使用尾部下划线而非头部下划线:大多数头部下划线的使用违反了ISO C标准,后跟大写字母的下划线尤其糟糕,因为这是新的C语言关键字使用的方式(_Bool_Complex_Generic_Atomic等)。 - Christoph
5个回答

8
不应该根据性能差异来决定使用宏还是函数。您应该仅基于函数的优点来评估它。一般情况下,选择函数。
宏有许多隐藏的缺点可能会给您带来麻烦。比如说,在这里将其转换为宏的翻译是不正确的(或者至少不保留原始函数的语义)。宏“distance”的参数每次都会被计算2次。想象一下我做了以下调用。
distance(GetPointA(), GetPointB());

在宏版本中,实际上会导致4次函数调用,因为每个参数都会被评估两次。如果将distance保留为一个函数,那么它只会导致3次函数调用(距离和每个参数)。注意:我忽略了上述计算中sqrtpow的影响,因为它们在两个版本中都相同。

3
我建议你使用内联函数而不是宏。它将为您提供宏的任何可能性好处,而没有丑陋。 (宏有一些陷阱,使它们非常不确定作为函数的一般替代品。特别是,宏参数在每次使用时都会被评估,而函数参数在“调用”之前只会被评估一次。)
inline float distance(CPoint p1, CPoint p2)
{
    float dx = p2.x - p1.x;
    float dy = p2.y - p1.y;
    return sqrt(dx*dx + dy*dy);
}

(注意,我还将pow(dx, 2)替换为dx * dx。两者等效,而乘法更有可能高效。一些编译器可能会尝试优化对pow的调用...但猜猜他们会用什么替换它。)

注:本段内容是关于代码优化方面的技术讨论,建议谨慎操作。

3
有三种东西:
- 像您上面的distance那样的普通函数 - 内联函数 - 预处理器宏
虽然函数可以保证某种类型安全,但由于每个函数调用都需要使用堆栈帧,因此它们也会导致性能损失。内联函数中的代码会复制到调用点,因此不会付出这种代价,但是您的代码大小将增加。宏没有类型安全性,并且涉及文本替换。
从这三者中选择,我通常会使用内联函数。只有在宏非常短并且以这种形式非常有用时(例如来自Linux内核的hlist_for_each),才使用宏。

3

Jared说得对,在这种特定情况下,pow调用和sqrt调用所花费的时间循环将比调用distance所花费的时间循环多两个数量级。

有时人们认为小代码等于少时间。但事实并非如此。


1

如果使用的是相当成熟的编译器,并且开启了优化,它可能会在汇编级别为您执行此操作。

对于gcc,-O3选项或(对于“小”函数)甚至-O2选项将执行此操作。

有关详细信息,您可以考虑阅读此处http://gcc.gnu.org/onlinedocs/gcc/Optimize-Options.html的“-finline *”选项。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接