Why are (member) function pointers behaving so wei

2019-01-22 12:33发布

问题:

I've had a really bizarre problem that I've reduced to the following test case:

#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");
}

It outputs

test1 will be called...
test1 will be called...

when optimizations are enabled, which is really bizarre. What's going on?

回答1:

This is a by-product of what Visual C++ refers to as Identical COMDAT Folding (ICF). It merges identical functions into a single instance. You can disable it by adding the following switch to the linker commandline: /OPT:NOICF (from the Visual Studio UI it is found under Properties->Linker->Optimization->Enable COMDAT Folding)

You can find details at the MSDN article here: /OPT (Optimizations)

The switch is a linker-stage switch, which means you won't be able to enable it just for a specific module or a specific region of code (such as __pragma( optimize() ) which is available for compiler-stage optimization).

In general, however, it is considered poor practice to rely on either function pointers or literal string pointers (const char*) for testing uniqueness. String folding is widely implemented by almost all C/C++ compilers. Function folding is only available on Visual C++ at this time, though increased widespread use of template<> meta-programming has increased requests for this feature to be added to gcc and clang toolchains.

Edit: Starting with binutils 2.19, the included gold linker supposedly also supports ICF, though I have been unable to verify it on my local Ubuntu 12.10 install.



回答2:

It turns out Visual C++'s linker can merge functions with identical definitions into one.
Whether that's legal or not according to C++, I have no idea; it affects observable behavior, so it looks like a bug to me. Someone else with more information may want to chime in on that though.



回答3:

C++11 5.3.1 describes what & does; in this instance, it gives you a pointer to the member function in question, and the passage makes no requirement that this pointer must be unique.

However, 5.10/1 says about ==:

Two pointers of the same type compare equal if and only if they are both null, both point to the same function, or both represent the same address.

The question then becomes... are test1 and test2 "the same function"?

Though the optimizer has collapsed them into a single definition, arguably the two names identify two functions and, as such, this would seem to be an implementation bug.

(Note, though, that the VS team don't care and consider it "valid enough" to warrant the benefits of the optimisation. That, or they don't realise that it's invalid.)

I'd stick to using the strings as "handles" for your function pointers.