有没有办法替换库中的一个函数?

5
我使用的是一个库,它为一种脚本语言定义了内部除法运算符。不幸的是,它没有对除数进行零检查。这导致了许多头痛。我知道运算符的签名。
double ScriptClass::Divide(double&, double&);
遗憾的是,它甚至不是一个C函数。有没有办法让我的应用程序使用我的自定义Divide函数而不是ScriptClass::Divide函数?
编辑: 我知道dlopen(NULL,..)和用用户定义的函数替换“C”函数的方法。但是类成员函数能否这样做(不使用编码后的名称)?

2
创建你自己的函数,并使用它?你甚至可以让你的函数调用 ScriptClass::Divide,但要在零检查中包装它。 - wkl
@birryree:如果那么简单,我会自己做的:D。ScriptClass通过解析脚本来创建其内部结构。99%的代码我不想再写一遍,我只想替换除法函数。 - nakiya
我知道我在这个问题中漏掉了什么。 - wkl
考虑到它是脚本输入,你不能在脚本端修复它吗?重写脚本,将所有出现的(a/b)替换为(a==0?NaN:a/b)(或者脚本可能有的任何语法)。 - MSalters
5个回答

4

有些链接器和动态链接器实现看起来提供了解决这个问题的方法,正如其他人所提到的。

然而,如果您使用其中任何一个功能(GNU ld 的 --wrap,ld.so 的 LD_PRELOAD 等)重新定义了一个 C++ 函数,则 您正在违反一个定义规则,因此会引发未定义行为。

在编译库时,编译器可以以任何它认为合适的方式内联所涉及的函数,这意味着您对该函数的重新定义可能不会在所有情况下被调用。

考虑以下代码:

class A
{
public:
    void foo();
    void bar();
};

void A::foo()
{
    std::cout << "Old version.\n";
}

void A::bar()
{
   foo();
}

当使用-O3选项调用GCC 4.5时,它实际上会决定将foo()的定义内联到bar()中。如果你以某种方式让链接器用自己的定义替换A::foo()的定义,A::bar()仍将输出字符串"Old version.\n"。
因此,简而言之:不要这样做。

那是个好观点,但你还有其他选择吗?我不确定“不要”是最有帮助的回答,因为这正是OP一直在做的。如果这对他有效,或者如果他有更简单的方法来做到这一点 - 那么他不会继续这样做了,对吧?而不是纠结于这种东西? :) - please delete me
我还是会+1的,因为我想如果这个函数只是一些小东西,只被调用了一次(对于脚本解释器中的除法函数来说很容易出现这种情况),那么这种情况很可能发生。 - please delete me
我真的想不出比“不要这样做”更有帮助的答案了。现在我想起来了,我可能会补充说,如果所讨论的函数是虚函数,那么成功的可能性还是相当大的。虽然它仍然是未定义行为,但需要一个非常聪明的优化器才能内联虚函数,所以这种情况可能不会发生。因此,如果该函数是虚函数且您无法从类中派生并覆盖它,则可以尝试一下。 - wolfgang
不,这个函数不是虚函数。如果它是,我就没有问题了。 - nakiya

3

3
通常情况下,防止除以零的责任在于程序员,而不是底层的除法运算符。如果你经常进行除以零的操作,这可能表明所使用的算法存在缺陷。考虑重新设计算法,或者如果无法更改算法,则通过进行零检查来保护调用除法函数。你甚至可以在一个protected_divide类型的函数内部进行检查。
话虽如此,假设由于看起来像C++函数,你拥有一个使用与构建应用程序相同选项编译的C++库,以便名称重整匹配,那么你可以将该函数重新定义为一个.so文件,并使用LD_PRELOAD强制加载它。如果链接静态库,我认为你可以将该函数创建到自己的.o文件中,并在链接库本身之前链接该文件,这样链接器将会选择你的版本。

我正在静态链接库。我尝试了你提到的第二种方法,链接器抛出了一个“多重定义”的错误,这是应该的。除非我能告诉链接器使用它遇到的第一个定义并丢弃所有其他定义,否则我不能使用它。第一种方法不能使用,因为有太多限制,我不想在这里提及。我希望有一个适合于二进制文件的解决方案。 - nakiya

0

我认为无法避免混乱的命名,但您可以使用ld的--wrap选项,使特定函数基于其旧名称获得新名称。然后,您可以编写一个新版本,并将其转发到旧版本(如果需要)。

这里是一个快速概述:

http://linux.die.net/man/1/ld

过去我曾经使用过这个来钩取malloc(等等),而无需重新编译运行时库,不过这并非在Linux上(它是一个嵌入式系统,没有运行时加载)。我没有用它来包装C++函数,但是如果你可以以某种方式处理C++调用约定,并且你可以创建一个带有原始函数名的函数,并让编译器接受对一个具有一些奇怪字符的丑陋名称的函数的调用...我不明白为什么不能使它工作。


0

简短问题, 你不能只是用自己的代码包装类吗? 在开始时可能会有些麻烦,但之后你可以简化很多函数。

(甚至可以用宏包装函数)


我一直在说,我没有使用这个函数 :). 我想改变另一个函数使用的函数,因为它会导致所有东西崩溃。如果我正在使用这个函数,就不会有问题了。 - nakiya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接