2

在我的开源软件项目中,我调用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 调用。这里有一个很好的描述它是如何工作

正如这里所讨论的,简单的解决方法是告诉他们修改 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”来实现。

4

2 回答 2

1

这是我的另一个工作解决方案。它可能在某些情况下存在,但我选择了上面的 makefile+script 解决方案。

此解决方案是在单独的源文件中提供 _sync_add_and_fetch_4、_sync_fetch_and_add_4、_sync_sub_and_fetch_4 和 _sync_fetch_and_sub_4 的本地定义。只有当编译器无法本地生成它们时,它们才会被链接。需要一些组装,但所有地方的维基百科都有一个我可以参考的合理实现。(我还反汇编了编译器通常生成的内容,以推断其他一切是否正确)。

#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
于 2013-02-24T01:41:15.987 回答
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) $^
于 2013-02-24T01:24:27.940 回答