没有。 C 变体带有#define-macros 的所有缺陷。提醒:
#define LERP(a,b,c) (((b) - (a)) * (c) + (a))
潜在的性能浪费
想象一个pure function call 在调用#define-宏:
int fac (int x) {
return x<=1 ? 1 : x*fac(x-1);
}
int main () {
std::cout << LERP(fac(5), fac(2), 0);
}
该行扩展为:
std::cout << (((fac(2)) - (fac(5))) * (0) + (fac(5)))
现在,您有可能将调用您的教员函数的两次运行时间加倍,而这之前只是一次。
如果嵌套 lerping,情况肯定会变得更糟,例如在某些图形编程情况下很常见:
int main () {
std::cout << LERP(
LERP(fac(5), fac(2), 0),
LERP(fac(5), fac(2), 0),
0
);
}
扩展到:
int main () {
std::cout << LERP(
(((fac(2)) - (fac(5))) * (0) + (fac(5))),
(((fac(2)) - (fac(5))) * (0) + (fac(5)))
0
);
}
扩展为(格式调整以提高可读性):
int main () {
std::cout << ( (((((fac(2)) - (fac(5))) * (0) + (fac(5))))
- ((((fac(2)) - (fac(5))) * (0) + (fac(5)))))
* (c)
+ ((((fac(2)) - (fac(5))) * (0) + (fac(5)))))
}
而干净的版本在计算上不超过:
float a = LERP(fac(5), fac(2), 0);
float b = LERP(fac(5), fac(2), 0);
float c = LERP(a,b,0);
或
float fac_a = fac(5),
fac_b = fac(2);
float a = (fac_b-fac_a)*0 + fac_a;
float fac_c = fac(5),
fac_d = fac(2);
float a = (fac_d-fac_c)*0 + fac_c;
所以在二维设置中
- 正确版本:
- 4 次调用
fac()
- 4 个补充
- 2 次乘法
- ´#define` 版本:
- 9 次致电
fac()
- 8 个新增功能
- 4 次乘法
随着您添加的每个维度,它都会变得更糟。甚至有时会看到五维 Perlin Noise(3d 体积 + 时间 + 连续种子),for which some expressions are evaluated freaking 31 times, instead of just once!:
LERP( LERP(LERP(LERP(LERP(probe(),1,2), LERP(3,4,5), 6),
LERP(LERP(7,8,9), LERP(10,11,12), 13),
14),
LERP(LERP(LERP(90,91,92), LERP(93,94,95), 96),
LERP(LERP(97,98,99), LERP(910,911,912), 913),
914),
1014),
LERP(LERP(LERP(LERP(0,1,2), LERP(3,4,5), 6),
LERP(LERP(7,8,9), LERP(10,11,12), 13),
14),
LERP(LERP(LERP(90,91,92), LERP(93,94,95), 96),
LERP(LERP(97,98,99), LERP(910,911,912), 913),
914),
1014),
666)
您还可以通过调用cpp 来查看预处理后的代码(注意之前probe() 的单一出现)。
foo@bar:~/ cpp heavy.cc
[snip] ((((((((((((((((911) - (910)) * (912) + (910)))) - (((98) - (97)))) * (99) + (97)))) * (913) + ((((98) - (97)) * (99) + (97))))) - (((((((94)) - (93)) * (95) + (93))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((((91) - ( 90)) * (92) + (90)))))) * (914) + (((((((94) - (93)) * (95) + (93))) - ((((( 91) - (90)) * (92) + (90)))) * (96) + ((((91) - (90)) * (92) + (90)))))) - ( ((((((((((11) - (10)) * (12) + (10)))) - ((((8) - (7)) * (9) + (7))))) * (13) + ((((8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3)) )) - ((((1) - (0)) * (2) + (0)))) * (6) + ((((1) - (0)) * (2) + (0)) )))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((((1) - (0)) * (2) + (0)))) * (6) + ((((1) - (0)) * (2) + (0)))))))) * (1014) + ((((((((() ((11) - (10)) * (12) + (10))) - ((((8) - (7)) * (9) + (7)))) * (13) + (((( (8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3)))) - (((( 1) - (0)) * (2) + (0)))) * (6) + ((((1) - (0)) * (2) + (0)))))) * (14 ) + (((((((4) - (3)) * (5) + (3))) - ((((1) - (0)) * (2) + (0)))) * (6) + ((((1) - (0)) * (2) + (0)))))))) - ((((((((((((((911) - (910)))) * (912) + (910))) - ((((98) - (97)) * (99) + (97)))) * (913) + ((((98) - (97)) * ( 99) + (97))))) - (((((((94) - (93)) * (95) + (93))) - ((((91) - (90))) * (92 ) + (90)))) * (96) + ((((91) - (90)) * (92) + (90)))))) * (914) + ((((((((94)) ) - (93)) * (95) + (93))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((((91) - (90)) * (92) + (90))))))) - (((((((((11) - (10)) * (12) + (10)))) - (( ((8) - (7)) * (9) + (7)))) * (13) + ((((8) - (7)) * (9) + (7))))) - ( (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe()))))) * (6) + ((((1) - (probe())) * (2) + (probe())))))) * (14) + ((((((((4)) - (3 )) * (5) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())))))))) * (1014) + (((((((((((11)) - (10))) * (12)) + (10))) - ((((8) - (7)) * (9) + (7)))) * (13) + ((((8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe()))) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())))))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((((1 ) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe())) )))))))) * (666) + (((((((((((((911) - (910))) * (912) + (910)))) - ((((98) - (97)) * (99) + (97)))) * (913) + ((((98) - (97)) * (99) + (97))))) - (((((( ((94) - (93)) * (95) + (93))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((( (91) - (90)) * (92) + (90)))))) * (914) + (((((((94) - (93)) * (95) + (93)))) - ((((91) - (90)) * (92) + (90)))) * (96) + ((((91) - (90)) * (92) + (90)))) ))) - ((((((((((11) - (10))) * (12) + (10)))) - ((((8) - (7)) * (9) + (7)) )))) * (13) + ((((8) - (7)) * (9) + (7))))) - (((((((4) - (3))) * (5 ) + (3))) - ((((1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe() )) * (2) + (probe())))))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((( (1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe() )))))))) * (1014) + ((((((((((11) - (10))) * (12) + (10))) - ((((8) - (7)) )) * (9) + (7)))) * (13) + (( ((8) - (7)) * (9) + (7))))) - (((((((4) - (3)) * (5) + (3)))) - ((( (1) - (probe())) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe() )))))) * (14) + (((((((4) - (3)) * (5) + (3))) - ((((1) - (probe()))) * (2) + (probe())))) * (6) + ((((1) - (probe())) * (2) + (probe()))))))))))
再次,完整源代码is here。
潜在的未定义行为
你可以把邪恶的东西放进去:
LERP(++a,b,c)
扩展为
(((b) - (++a)) * (c) + (++a))
这是 C¹(和 C++,顺便说一句)中未定义的行为。 a 可能会增加两次,也可能会增加一次,或者可能会抛出异常 Debug-Runtime-Exception: Invoked Undefined Behaviour,或者编译器足够聪明,可以拒绝该代码,等等。
未定义行为源于 C99 标准(以及 C++2003)不允许在到达下一个 Sequence Point 之前多次修改值。
ID 污染和感染
(如果您将 C# 转换为宏变体,这更相关。)
#define-macro-name 污染并感染了从定义点到单元结尾或未定义的整个翻译单元。
foo@bar:~/ cat main.cc
// Orbiter Physics Sim
#include "lerp.h"
int main () {
const int LERP = 2; // Linear Extrasolar Resonance Potential.
}
foo@bar:~/ g++ main.cc
main.cc:5:15: error: expected unqualified-id before ‘=’ token
更多...
- 宏是通用的,但不是类型安全的。宏编写者没有(干净的)机会对该宏应该有效的类型施加约束。
- 宏没有作用域,尽管上一节已经暗示了这一点
¹:C99 (ISO/IEC 9899:TC2),J.2,“未定义行为”:Between two sequence points, an object is modified more than once, or is modified and the prior value is read other than to determine the value to be stored (6.5).