在我的开源软件项目中,我调用gcc atomic builtins:__sync_add_and_fetch和__sync_sub_and_fetch来实现某些变量的原子增量和减量。我定期收到某人试图编译我的代码的电子邮件,但他们收到以下链接器错误:
refcountobject.cpp:(.text+0xb5): undefined reference to `__sync_sub_and_fetch_4'
refcountobject.cpp:(.text+0x115): undefined reference to `__sync_add_and_fetch_4'
经过一番挖掘后,我将根本原因缩小到了他们的旧版gcc(4.1)默认为i386的目标架构这一事实。显然,gcc实际上并没有80386上的原子添加内在函数,所以它隐含地在其中注入一个未定义的__sync_add_and_fetch_4调用。有关其工作原理的详细说明是here。
正如所讨论的here,简单的解决方法是告诉他们修改Makefile以附加 -march = pentium 作为编译器标志之一。一切都很好。
那么长期修复是什么,以便用户无需手动修复Makefile?
我正在考虑一些想法:
我不想将-march = pentium硬编码为Makefile中的编译器标志。我猜这将打破任何非基于英特尔的东西。但是如果Makefile有规则来检测默认目标是i386,我当然可以添加它。我正在考虑在Makefile中有一个规则,它是一个调用gcc -dumpmachine并解析出第一个三元组的脚本。如果字符串是i386,它将添加编译器标志。我假设没有人会为80386机器建造。
另一种选择是实际为__sync_add_and_fetch_4提供实现,以便链接器可以依赖。它甚至可以根据定义的GCC_HAVE_SYNC_COMPARE_AND_SWAP宏的存在而有条件地编译。我用全局pthread_mutex原型化了一个实现。可能不是最好的表现,但它可以很好地解决问题。如果编译x86,更好的想法可能是自己编写内联汇编来调用“lock xadd”。
答案 0 :(得分:1)
这是我的其他工作解决方案。它可能在某些情况下有它,但我选择了上面的makefile +脚本解决方案。
此解决方案是在单独的源文件中为_sync_add_and_fetch_4,_sync_fetch_and_add_4,_sync_sub_and_fetch_4和_sync_fetch_and_sub_4提供本地定义。只有在编译器无法原生生成它们时,它们才会被链接。需要一些装配,但所有地方的Wikipedia都有合理的实施,我可以参考。 (我还反汇编了编译器通常生成的内容,以推断其他一切是否正确)。
#if defined(__i386) || defined(i386) || defined(__i386__)
extern "C" unsigned int xadd_4(volatile void* pVal, unsigned int inc)
{
unsigned int result;
unsigned int* pValInt = (unsigned int*)pVal;
asm volatile(
"lock; xaddl %%eax, %2;"
:"=a" (result)
: "a" (inc), "m" (*pValInt)
:"memory" );
return (result);
}
extern "C" unsigned int __sync_add_and_fetch_4(volatile void* pVal, unsigned int inc)
{
return (xadd_4(pVal, inc) + inc);
}
extern "C" unsigned int __sync_sub_and_fetch_4(volatile void* pVal, unsigned int inc)
{
return (xadd_4(pVal, -inc) - inc);
}
extern "C" unsigned int __sync_fetch_and_add_4(volatile void* pVal, unsigned int inc)
{
return xadd_4(pVal, inc);
}
extern "C" unsigned int __sync_fetch_and_sub_4(volatile void* pVal, unsigned int inc)
{
return xadd_4(pVal, -inc);
}
#endif
答案 1 :(得分:0)
没有回复,我自己解决了这个问题。
有两种可能的解决方案,这是其中之一。
首先,将以下脚本getfixupflags.sh添加到与Makefile相同的目录中。此脚本将检测编译器是否可能以i386为目标,如果是,则将“-march = pentium”作为输出回显。
#!/bin/bash
_cxx=$1
_fixupflags=
_regex_i386='^i386'
if [[ ! -n $_cxx ]]; then echo "_cxx var is empty - exiting" >&2; exit; fi
_target=`$_cxx -dumpmachine`
if [[ $_target =~ $_regex_i386 ]]; then
_fixupflags="$_fixupflags -march=pentium"
fi
if [[ -n $_fixupflags ]]; then echo $_fixupflags; fi
现在修复Makefile以使用此脚本。将以下行添加到Makefile
FIXUP_FLAGS := $(shell getfixupflags.sh $(CXX))
然后在编译代码时修改Makefile中的编译器指令以包含FIXUP_FLAGS。例如:
%.o: %.cpp
$(COMPILE.cpp) $(FIXUP_FLAGS) $^