Подтвердить что ты не робот

Почему (элементарные) указатели функций ведут себя так странно в Visual С++?

У меня была действительно странная проблема, которую я привел к следующему тестовому примеру:

#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 будет вызываться...

когда оптимизация включена, что действительно странно. Что происходит?

4b9b3361

Ответ 1

Получается, что компоновщик Visual С++ может объединять функции с одинаковыми определениями в один. Является ли это законным или нет в соответствии с С++, я понятия не имею; он влияет на наблюдаемое поведение, поэтому он выглядит как ошибка для меня. Кто-то другой с дополнительной информацией может захотеть услышать это.

Ответ 2

Это побочный продукт того, что Visual С++ называется Идентичным Складкой COMDAT (ICF). Он объединяет идентичные функции в один экземпляр. Вы можете отключить его, добавив следующий переключатель в командную строку компоновщика: /OPT:NOICF (из пользовательского интерфейса Visual Studio он находится в разделе "Свойства- > Линетер- > Оптимизация- > Включить СОДЕРЖАНИЕ COMDAT)

Подробности в статье MSDN можно найти здесь: /OPT (Оптимизация)

Коммутатор - это переключатель компоновщика, что означает, что вы не сможете включить его только для определенного модуля или определенной области кода (например, __pragma( optimize() ), которая доступна для оптимизации на этапе компиляции).

В целом, однако, считается, что плохой практикой полагаться на указатели на функции или литературные указатели строк (const char*) для проверки уникальности. Сгибание строк широко реализуется почти всеми компиляторами C/С++. Функция фальцовки доступна только на Visual С++ в это время, хотя широко распространенное использование шаблона < > мета-программирования увеличило запросы на добавление этой функции в gcc и clang toolchains.

Изменить: начиная с binutils 2.19, включенный золотой линкер, предположительно, также поддерживает ICF, хотя я не смог проверить его на своей локальной установке Ubuntu 12.10.

Ответ 3

С++ 11 5.3.1 описывает, что делает &; в этом случае он дает вам указатель на функцию-член, о которой идет речь, и этот отрывок не требует, чтобы этот указатель был уникальным.

Однако 5.10/1 говорит о ==:

Два указателя одного и того же типа сравниваются равными, если и только если оба они равны нулю, оба указывают на одну и ту же функцию или оба представляют один и тот же адрес.

Затем вопрос становится... test1 и test2 "той же функцией"?

Хотя оптимизатор сворачивает их в одно определение, возможно, два имени идентифицируют две функции и, как таковые, , это может показаться ошибкой реализации.

(Обратите внимание, что команда VS не заботится и считает это "достаточно обоснованным", чтобы гарантировать преимущества оптимизации. Это или они не понимают, что он недействителен.)

Я хотел бы использовать строки как "дескрипторы" для ваших указателей на функции.