我正在尝试决定是将某些操作实现为宏还是函数。
说,举个例子,我在头文件中有以下代码:
extern int x_GLOB;
extern int y_GLOB;
#define min(x,y) ((x_GLOB = (x)) < (y_GLOB = (y))? x_GLOB : y_GLOB)
意图是每个单个参数只计算一次(min(x++,y++)
不会导致任何问题)。
问题是:这两个全局变量在代码重入或线程安全方面是否存在任何问题?
我会说不,但我不确定。
那怎么样:
#define min(x,y) ((x_GLOB = (x)), \
(y_GLOB = (y)), \
((x_GLOB < y_GLOB) ? x_GLOB : y_GLOB)
这是一个不同的案例吗?
请注意,问题不是“一般”,而是与这些全局变量仅在该宏内使用以及评估单个表达式有关。
无论如何,看看下面的答案,我想我可以总结如下:
这不是线程安全的,因为没有什么可以保证线程在表达式的评估中“暂停”(正如我所希望的那样)
这些全局代表的“状态”至少是“min”操作的内部状态,并且保留该状态至少需要对如何调用函数施加限制(例如避免min(min(1,2),min(3,1))。
我认为使用不可移植的构造也不是一个好主意,所以我想唯一的选择是保持安全方面的工具作为常规函数。
答案 0 :(得分:4)
静默修改像min
这样的宏内部的全局变量是一个非常糟糕的主意。你好多了
min
可以多次评估其参数(在这种情况下,我称之为MIN
使其看起来不像普通的C函数),或者min
或如果您执行上述任何操作,则可以完全消除全局变量,从而解决有关重入和多线程的任何问题。
选项1:
#define MIN(x, y) ((x) < (y) ? (x) : (y))
选项2:
int min(int x, int y) { return x < y ? x : y; }
当然,这有一个问题,就是你不能将这个版本的min
用于不同的参数类型。
选项3:
#define min(x, y) ({ \
typeof(x) x__ = (x); \
typeof(y) y__ = (y); \
x__ < y__ ? x__ : y__; \
})
答案 1 :(得分:2)
全局变量不是线程安全的。您可以使用thread-local storage。
来制作它们就个人而言,我会使用内联函数而不是宏来完全避免这个问题!
答案 2 :(得分:1)
一般来说,只要你使用全局变量,你的子程序(或宏)就不是可重入的。
任何时候两个线程访问相同的数据或共享资源,您必须使用同步原语(互斥,信号量等)来协调对“关键部分”的访问。
请参阅Wikipedia reentrant页面
答案 3 :(得分:1)
这不是线程安全的。为了证明这一点,假设线程A调用min(1,2)并且threadB调用min(3,4)。假设线程A首先运行,并且由宏的问号处的调度程序中断,因为它的时间片已经过期。
然后x_GLOB为1,y_GLOB为2。
现在假设threadB运行一段时间,并完成宏的内容:
x_GLOB为3,y_GLOB为4。
现在假设threadA恢复。它将返回x_GLOB(3)而不是正确的答案(1)。
显然我已经简化了一些事情 - 在问号上被中断是非常复杂的,并且threadA不一定返回3,在一些具有一些优化级别的编译器上它可能将值保存在寄存器中而不是读取从x_GLOB返回。因此,发出的代码可能恰好与该编译器和选项一起使用。但希望我的例子能说明问题 - 你无法确定。
我建议你写一个静态内联函数。如果你想要非常便携,可以这样做:
#define STATIC_INLINE static
STATIC_INLINE int min_func(int x, int y) { return x < y ? x : y; }
#define min(a,b) min_func((a),(b))
然后,如果您在某个特定平台上遇到性能问题,并且结果是因为编译器无法内联它,您可能会担心是否需要在该编译器上以不同方式定义STATIC_INLINE(static inline
在C99中,或static __inline
用于Microsoft,或其他任何方式),或者甚至可能以不同方式实现min
宏。这种优化级别(“它内联吗?”)不是你在便携式代码中可以做的很多。
答案 4 :(得分:1)
就我个人而言,即使我们不考虑多线程场景,我也不认为这段代码是安全的。
进行以下评估:
int a = min(min(1, 2), min(3, 4));
如何正确扩展和评估?
或者我在这里遗漏了什么?
答案 5 :(得分:0)
该代码对多线程不安全。
除非必不可少,否则请避免使用宏(“高效C ++”书中有这些案例的例子)。