【问题标题】:#define conversion from C to C##define从C到C的转换#
【发布时间】:2011-11-05 23:45:19
【问题描述】:

这是C代码吗:

/* LERP(a,b,c) = linear interpolation macro, is 'a' when c == 0.0 and 'b' when c == 1.0 */
#define LERP(a,b,c)     (((b) - (a)) * (c) + (a))

http://www.brucelindbloom.com/index.html?Eqn_XYZ_to_T.html

等于这个 C# 代码?

private static double LERP(double a, double b, double c) { return (((b) - (a)) * (c) + (a)); }

?

【问题讨论】:

  • 那里的括号有点太重了,但看起来合法。
  • @bobobobo:如果出现一个答案,你的上帝般的意见不考虑“废话”,但你的“废话评论”仍然存在怎么办?你会每天检查这个问题的主题吗?仅这一点就足以证明你的意见对于你的非防御性说话风格是无稽之谈,这也反映了你的编程风格。由于您不知道和/或不尊重在 StackOverflow 上投反对票的共同理由,这一点具有双重意义。请永远不要在我的公司工作。至少不在 IT 领域。
  • 很好。您的回答很好,但仅提及 ++ 极端情况或需要从整数类型转换的事实并不是很有用。
  • @bobobobo:对于浮点类型,增量运算符也有很好的定义。

标签: c# c macros


【解决方案1】:

没有。 考虑以下几点:

LERP(x++,1,2);

c 代码也可能具有将 x 增加两倍的副作用[@phresnel 提到它是未定义的],而 c# 代码是完美定义的,并且只会增加 x 一次。

结果也可能不同,因为第一个 a 和第二个 [在宏中] 可能具有不同的值,因为它可能在第一个中增加了。

【讨论】:

  • +1,即为什么不应该首先使用宏。但是想赌一百万美元说 OP 不关心极端情况吗? :)
  • @Gleno:这听起来像是一个家庭作业问题,正是在寻找这个极端案例......无论如何,人们也应该考虑这个问题
  • @amit Thx,没有注意到角落案例。无论如何要修复它?
  • @Danpe:使用函数而不是宏:)
  • @bobobobo:OP 询问 2 个代码示例是否相等,相等的定义:所有输入案例的行为相同。我的回答旨在展示一个示例,其中相同输入的行为不同,因此“功能”不相等。
【解决方案2】:

没有。 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;

所以在二维设置中

  1. 正确版本:
    1. 4 次调用fac()
    2. 4 个补充
    3. 2 次乘法
  2. ´#define` 版本:
    1. 9 次致电fac()
    2. 8 个新增功能
    3. 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).

【讨论】:

  • 我认为您指出的问题仅适用于 c++,它不是在 c 中完美定义的吗? [这个问题被标记为'c',而不是'c++']
  • 也适用于 C99。参见 J2 部分,“未定义行为”的摘要: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).
  • 我编辑了我的答案以反映这一点,谢谢指出我对 C/C++ 的困惑 :)
【解决方案3】:

从技术上讲,不,它们不相等。 C 宏可以采用任何类型:intfloatbyte 等。

您的 C# 版本只能处理 double,没有显式转换。您需要根据需要为其他类型添加重载。

【讨论】:

  • LERP 在整数类型上几乎没用
  • @bobobobo:所以?它仍然可以采用定义了这些运算符的任何类型。除此之外,只有这种线性插值的化身是无用的。但一般来说,lerping 整数是完全有效的。你只需要一些除法或位移。我已经在 metatrace 中这样做了,例如,基于定点数学的编译时光线跟踪器(整数可以被认为是定点数的子集)。
  • @bobobobo:问题:如果您是对这个正确答案投反对票的人,那么您一定是真的缺乏经验。编程和堆栈溢出社区。​​span>
【解决方案4】:

基本上是等价的,是的。你也可以去掉一些括号:

private static double LERP(double a, double b, double c) { return (b - a) * c + a; }

【讨论】:

  • 基本不等价。宏不是类型安全的。
  • 我把这个问题解释为“如果我这样重写,它会做同样的计算吗?”。
  • 对不起,不是这样写的。
  • 有时我们只需要读心术,回答应该问的问题。 OP 在 C# 中需要一个与 C 中的宏等效的函数,这就是等效函数。
  • @Dialectus:我明白你在说什么,但是一组等效的函数将具有所有重载。宏接受任何东西。此外,这不等效,因为宏有副作用,如当前最高投票的答案所示。
【解决方案5】:

要获得你在 C 中的内容,你可以使用:

delegate double ValueGetter();
static double LERP(ValueGetter a, ValueGetter b, ValueGetter c) { return (b() - a()) * c() + a(); }

但我同意阿米特。这可能与您在 C 中的相同(但仅适用于 double 而不是其他类型),但它可能不是您真正想要的..(使用委托,您可以将 'i++' 设置为 a,而不仅仅是 i++ 的结果)

【讨论】:

  • 但是,C 版本 LERP(i++,... 的结果仍然没有定义,因此对于多个分配,不可能完全重复。
  • 确实如此。我的例子甚至不是通用的。但即使是这样,它也不会涵盖宏的所有可能性。
【解决方案6】:

是的,但你可以写得更简单:

返回 (b - a) * c + a;

【讨论】:

  • -1:实际上它们并不相等,正如其他答案所指出的那样(来自#define-macros 的缺陷)
  • @phresnel - 此 C# 代码的工作方式与正确使用的宏类似,并且在使用不正确时会出现编译错误。你可以吗?如果您获得 40% 的反对票,您就接近获得巨魔徽章了。
  • 问题是它们是否等效,恐怕它们不是。什么是“正确使用”?例如:centers[x] = lerp(++minsIter, ++maxsIter, 0.5);,这对我来说甚至看起来都不脏。它可能用于 C# 版本,但不适用于 C-Macro 版本。巨魔徽章:你说得对,我必须投票更多。
猜你喜欢
  • 1970-01-01
  • 2017-03-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多