问题提出:
在研究 emacs lisp 的时候, 文件 lisp.h 里面有一个宏的写法有点特殊, 引起了我的奇怪.
#define XFLOAT_DATA(f) (0 ? XFLOAT (f)->u.data : XFLOAT (f)->u.data)
其中为了简化问题, 我们将这个宏简写一下:
#define XFLOAT_DATA(f) (0 ? f->data : f->data)
在宏中使用了三元操作符 "? :", 但前后的值一样, 并且判断条件是常量 0, 这是怎么回事?
难道作者闲得无聊才这么做么? 问题可不这么简单吧...
问题思考和解决:
再参考一下另一个宏, 也许对判断作者的意图有所启发:
#define SSIZE(str) (str->size + 0)
在这个宏中, size 是一个整形字段, 所以加上 0 并不会改变最后的 size 值, 不过这个表达式
使得 SSIZE(str) 成为右值 (rvalue) 表达式了, 所以其可以读取, 但不能写入了:
int len = SSIZE(str); // 合法, SSIZE(str) 是作为右值
SSIZE(str) = len; // 编译错误, SSIZE(str) 即 (str->size + 0) 不能作为左值.
这样, 我们猜测将 XFLOAT_DATA() 宏写成那个样子, 也是有着同样的意图的.
但没有使用 (f->data + 0) 的却另有原因了. 因为编译器对于整形运算可以施加各种优化,
如 size+0 可直接优化为 size, 但是对于浮点数 f+0 却不一定做这样的优化.
如果宏写为 (f->data + 0) 会带来额外的运行时开销, 这种开销是难以接受的, 对于处处
考虑性能的 C/C++ 程序猿而言.
所以, 写成了 (0 ? f->data : f->data) 这种奇怪的表达式方式了.
可是事情还没完, 老编译器不能将 "?:" 表达式作为左值使用, 但是新编译器却可以:
int x = 1, y, z;
(x ? y : z) = 3; // 竟然合法, 结果是 y = 3.
XFLOAT_DATA(f) = 3.14; // 竟然合法! 作者意图未达到...
所以, 辛苦写为 (0 ? f->data : f->data) 无法达到阻止左值的意图, 如果作者真的唯一意图如此的话.
最后, 考虑的一种解决方式还是用我们的老朋友 const 关键字:
#define XFLOAT_DATA(f) ((const double) (f->data))
这下表达式 "XFLOAT_DATA(f) = 3.14" 编译器终于可以阻止其编译了.
error C2106: “=”: 左操作数必须为左值.
可能的错误也就可以避免了. 也许也就拯救了地球说不定...