29

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?

Alexander Dunaev
  • 980
  • 1
  • 15
  • 40
user541686
  • 205,094
  • 128
  • 528
  • 886
  • 1
    Using function pointers as unique identifying "handles" in this way seems like a bit of a non-starter, for precisely this reason. – Lightness Races in Orbit Jan 05 '13 at 21:01
  • @LightnessRacesinOrbit: I guess I found out the hard way, lol. – user541686 Jan 05 '13 at 21:03
  • @pmg: Why? I specifically added the C tag because it affects C too, it helps people find the question. – user541686 Jan 05 '13 at 21:05
  • 7
    @Mehrdad: Because the question is about C++, and the testcase is written in C++, and C++ is a different language to C with different rules. – Lightness Races in Orbit Jan 05 '13 at 21:05
  • @LightnessRacesinOrbit: Uhm, this isn't a language issue, it's a linker issue. C code that compares function pointers produces exactly the same problem, so I'm putting back the tag to make it easier for future searchers to find this. There's really no distinction between C and C++ for this behavior aside from the particular test case I had here... it's not like the linker knows what language the code was compiled in. Just try comparing function pointers and you'll get the same exact result. – user541686 Jan 05 '13 at 21:06
  • @Mehrdad: If it's not a language issue then kindly remove the C++ tag and give us an assembly testcase, not a C++ testcase. Frankly I don't really care how easy it is for some future person to search for this; if they know how to use Google they can find it tagged `c++`. – Lightness Races in Orbit Jan 05 '13 at 21:09
  • 1
    @LightnessRacesinOrbit: Well I guess I disagree; I *do* care how easy it is for some future person to search for this -- that was the *entire reason* I posted this question in the first place! (Notice that I posted my own answer...) Anyway, let's stay sensible and not get needlessly pedantic... this is a linker issue and it behaves the exact same way for C and C++. Heck, if I had assembly code in front of me to reproduce it, I'd tag it as that too. – user541686 Jan 05 '13 at 21:12
  • 1
    @Mehrdad: Okay; then might I suggest improving the question's title? At present it doesn't describe the problem. It just says that things are "weird". – Lightness Races in Orbit Jan 05 '13 at 21:13
  • @LightnessRacesinOrbit: Sure, parentheses added for your reading pleasure. Now it applies to both C and C++. – user541686 Jan 05 '13 at 21:14
  • @Mehrdad: I meant to replace the title with one that describes the problem, other than "behaving so weirdly" – Lightness Races in Orbit Jan 05 '13 at 21:14
  • @LightnessRacesinOrbit: Any suggestions on what to change it to? – user541686 Jan 05 '13 at 21:15
  • @Mehrdad: Not at this time – Lightness Races in Orbit Jan 05 '13 at 21:15
  • 4
    @LightnessRacesinOrbit Please stay constructive. "I don't care about anybody who wants to google this issue" and "change it now but IDK what to change it" sounds selfish. –  Jan 05 '13 at 21:25
  • @H2CO3: It's not for me. It's for everybody. Same reason we have voting and edits. I don't have a better title right now but that doesn't mean that this one is good. – Lightness Races in Orbit Jan 05 '13 at 21:27
  • 1
    @LightnessRacesinOrbit: Well you might have *meant* it to be for everybody, but "I don't care" never implies that. Word choice is key in situations like this. :) – user541686 Jan 05 '13 at 21:29
  • 2
    @Mehrdad: I chose that phrase not without precedent. See Andromeda wherein Trance says "I don't care about the bones", implying not that she has an expressly negative personal and emotional attachment to them but instead that her experience and wisdom has placed no great importance on them, in contrast with the claims of others nearby that the bones had significant value. Similarly here I meant only that, in my view, tagging questions _correctly_ is more important than maximising search engine hits. Otherwise let's tag the question `php`, `java` and `mysql` too to get _everyone_ involved! =) – Lightness Races in Orbit Jan 05 '13 at 21:41
  • @LightnessRacesinOrbit: Context is another one. ;) And no one was trying to "get everyone involved", since there is nothing to be "involved" *in*... the answer was already there. If you're still missing my point then I give up. – user541686 Jan 06 '13 at 00:15
  • If someone would open a new bug on [Microsoft Connect](http://connect.microsoft.com/visualstudio), that would be wonderful. – James McNellis Jan 06 '13 at 07:37
  • 2
    @JamesMcNellis: [Done!](https://connect.microsoft.com/VisualStudio/feedback/details/775899/member-functions-merged-by-linker-causing-incorrect-behavior) Feel free to ping me if you notice them post a follow-up that I don't notice within a day or so. – user541686 Jan 06 '13 at 07:46
  • If I understood this correctly, this is specific to Visual C++, is that correct? I am not able to compile the code on solaris – Alex Jan 23 '13 at 21:52
  • 1
    @Alex: Yes... if your compiler/linker doesn't merge duplicate functions then this isn't going to happen. – user541686 Jan 23 '13 at 22:56
  • Related to: [Do distinct functions have distinct addresses?](http://stackoverflow.com/q/26533740/1708801) CC @JamesMcNellis I am curious if you think this is compliant behavior, in this [discussion](https://social.msdn.microsoft.com/Forums/vstudio/en-US/dd91dae2-6f9d-4a46-b1dd-753c2b927119/function-pointers-and-unwanted-optimization?forum=vclanguage) Hans believes it is implementation detail. – Shafik Yaghmour Nov 15 '14 at 03:15

3 Answers3

27

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.

jstine
  • 3,234
  • 20
  • 21
  • *In general, however, it is considered poor practice to rely on either function pointers* ... I agree with this and I don't see why anyone would need to rely on this. See a more general discussion [here](http://stackoverflow.com/q/26533740/1708801) – Shafik Yaghmour Nov 17 '14 at 20:32
18

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.

user541686
  • 205,094
  • 128
  • 528
  • 886
7

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.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • +1, but I would suggest you use int as "handles" as they are faster to compare. – Caesar Jan 05 '13 at 21:22
  • @Caesar: Not if you factor in the time to create and maintain a look-up for those integers. – Lightness Races in Orbit Jan 05 '13 at 21:23
  • 3
    @Caesar: Why not `enum class`? they're *fast* as well as they've **names**! – Nawaz Jan 05 '13 at 21:23
  • They both represent the same address. Therefore Visual C++'s optimization is still standards compliant. – jstine Jan 06 '13 at 17:16
  • Downvoted for exercising baseless anti-Visual C++ sentiment and misinterpretation of the standard. – jstine Jan 06 '13 at 17:45
  • 1
    @jstine: Perhaps you can clarify precisely what it is about the standard that I have misinterpreted. As for VC++, it's not baseless -- in the thread I linked to, Passant and his cohorts explicitly claim that the functionality is not a bug, even though it is. These conclusions of mine _do_ rely on the notion that I haven't made a mistake; I'm certainly open to the possibility that I have, but perhaps you could identify it for me rather than simply screaming "you're wrong", downvoting and running away? – Lightness Races in Orbit Jan 06 '13 at 17:55
  • I did explain it. But apparently your emotion on the matter is inhibiting your judgement. I will try again. "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." Notice the last part. Furthermore, it is a fact that the Standard Committee left in the option to fold functions, specifically because of redundancy that can occur from use of template<> meta-programming (prime examples include T and T, which are technically different function names but generate identically on x64) – jstine Jan 06 '13 at 18:22
  • Agreed, I've removed my off-topic comment. So I shall now await you to do your part and discuss with me how "[...] or both represent the same address." is not being satisfied by Visual C++'s behavior. – jstine Jan 07 '13 at 01:02
  • @jstine: That's a tricky one. My initial interpretation was that functions don't semantically have "addresses" _per se_ at this level, and that therefore the "or both represent the same address" could only be intended to refer to _objects_. Otherwise why bother with the "point to the same function" clause? I stand by this interpretation of intent, but I can't find a way to objectively defend the viewpoint that this is how the passage's meaning should be _inferred_. Still working on it ... – Lightness Races in Orbit Jan 07 '13 at 01:07
  • @jstine: I've taken it to http://stackoverflow.com/q/14188612/560648. I'll feed the conclusion back into this answer if my mind has been changed. – Lightness Races in Orbit Jan 07 '13 at 01:22