处理__sync_add_and_fetch未定义

时间:2013-02-21 23:48:40

标签: multithreading gcc atomic

在我的开源软件项目中,我调用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”。

2 个答案:

答案 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) $^