Почему (элементарные) указатели функций ведут себя так странно в 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 будет вызываться...
когда оптимизация включена, что действительно странно. Что происходит?
Ответы
Ответ 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 не заботится и считает это "достаточно обоснованным", чтобы гарантировать преимущества оптимизации. Это или они не понимают, что он недействителен.)
Я хотел бы использовать строки как "дескрипторы" для ваших указателей на функции.