29

我遇到了一个非常奇怪的问题,我将其简化为以下测试用例:

#include <iostream>
#include <map>
#include <string>

struct Test
{
    std::map<std::string, void (Test::*)()> m;
    Test()
    {
        this->m["test1"] = &Test::test1;
        this->m["test2"] = &Test::test2;
    }
    void test1() { }
    void test2() { }
    void dispatch(std::string s)
    {
        if (this->m.at(s) == &Test::test1)
        { std::cout << "test1 will be called..." << std::endl; }
        else if (this->m.at(s) == &Test::test2)
        { std::cout << "test2 will be called..." << std::endl; }
        (this->*this->m.at(s))();
    }
};

int main()
{
    Test t;
    t.dispatch("test1");
    t.dispatch("test2");
}

它输出

test1 将被调用...
test1 将被调用...

当启用优化时,这真的很奇怪。这是怎么回事?

4

3 回答 3

27

这是 Visual C++ 称为相同 COMDAT 折叠(ICF) 的副产品。它将相同的功能合并到一个实例中。您可以通过将以下开关添加到链接器命令行来禁用它:(/OPT:NOICF 在 Visual Studio UI 中,它位于Properties->Linker->Optimization->Enable COMDAT Folding下)

您可以在此处的 MSDN 文章中找到详细信息:/OPT(优化)

该开关是链接器阶段开关,这意味着您将无法仅针对特定模块或特定代码区域(例如__pragma( optimize() )可用于编译器阶段优化的区域)启用它。

然而,一般来说,依赖函数指针或文字字符串指针 ( const char*) 来测试唯一性被认为是不好的做法。几乎所有 C/C++ 编译器都广泛实现了字符串折叠。函数折叠目前仅在 Visual C++ 上可用,尽管模板<> 元编程的广泛使用增加了将此功能添加到 gcc 和 clang 工具链的请求。

编辑:从 binutils 2.19 开始,包含的黄金链接器据说也支持 ICF,尽管我无法在我的本地 Ubuntu 12.10 安装上验证它。

于 2013-01-06T17:22:32.293 回答
18

事实证明,Visual C++ 的链接器可以将具有相同定义的函数合并为一个。
根据 C++ 是否合法,我不知道;它会影响可观察的行为,所以对我来说它看起来像一个错误。不过,其他有更多信息的人可能想插话。

于 2013-01-05T20:55:39.633 回答
7

C++11 5.3.1 描述了什么&;在这种情况下,它为您提供了一个指向相关成员函数的指针,并且该段落没有要求该指针必须是唯一的。

但是,5.10/1 说==

相同类型的两个指针比较相等当且仅当它们都为空,都指向同一个函数,或都表示相同的地址。

那么问题就变成了......是test1test2“相同的功能”吗?

尽管优化器已将它们合并为一个定义,但可以说这两个名称标识了两个函数,因此,这似乎是一个实现错误

(但请注意,VS 团队并不关心并认为它“足够有效”以保证优化的好处。那,或者他们没有意识到它是无效的。)

我会坚持使用字符串作为函数指针的“句柄”。

于 2013-01-05T21:19:22.607 回答