禁用特定MSVC版本的优化

Disable optimization on specific MSVC version

本文关键字:版本 优化 MSVC      更新时间:2023-10-16

我有一个使用CMake的项目,由于编译器错误(模板解析,优化和异常处理之间的交互),无法在VS 2015上编译此代码。

这个bug可以通过禁用优化来避免——虽然这会产生次优的代码,但至少项目可以编译。

如何在ReleaseRelWithDebInfo构建中将MSVC 2015的默认优化级别更改为/O0 ?

我的naïve实现将是CMAKE_CXX_FLAGS中的条件替换-未来安全吗?

另一种方法是在违规的标题中使用#pragma

这个编译器错误使链接器与垃圾一起烦恼,尽我所能追踪问题(VS2015是新的,在撰写本文时没有发布补丁/更新)。崩溃发生在链接器中。编译器似乎认为一切顺利。

当这个bug被"激发"时,问题在使用生成的模板对象的代码中。换句话说,即使您在标题中禁用了优化,但在正文中重新启用了优化,它仍然会使链接器崩溃。"有效"的是禁用对实例化和使用模板对象的成员函数的代码的优化(您可以对该目标之外的所有代码保持优化)。

例如,在问题中发布的代码中,在整个头中保持优化。对于使用模板的函数,执行:

#pragma optimize( "", off )

void test()
{
    two<one<int> > obj;
    obj.func();
}
#pragma optimize( "", on )

这隔离了对引发问题的代码的优化损失,并且链接器成功。

当然,pragma本身可以用条件定义或其他机制包装,一旦VS2015的补丁发布修复了这个问题,你可以禁用这些机制。

通过这种方式,代码可以在不考虑构建配置的情况下使用(意思是,它将适用于CMAKE构建和IDE构建),而不必给代码的后续用户带来负担(除了定义来控制是否禁用优化)。

如果它恰好适合你的情况,你也可以尝试这样做:

template<typename T>
struct two
{
    T t;
    void func()
    {
        typedef typename T::type type;
        type i = std::numeric_limits<type>::min();
        type j = std::numeric_limits<type>::epsilon();
        t.t1 = i / 2 + j;
        t.t2 = t.t1;
    }
};

这种重写代码绕过了这个错误,并且编译时不会导致链接器崩溃。

同样,这也绕过了这个问题:

   void func()
    {
        typedef typename T::type type;
        t.t1 = std::numeric_limits<type>::min() / 2 + std::numeric_limits<type>::epsilon();
        t.t2 = t.t1;
    }

而且,这对我来说很奇怪,并且代表了稍微更好的设计(因为它不需要一个类来摆弄另一个类的成员)

template<typename T>
struct one
{
    typedef T type;
    T t1, t2;
    void set( const T & i ) { t1 = t2 = i; }
};

template<typename T>
struct two
{
    T t;
    void func()
    {
        typedef typename T::type type;
        t.set( std::numeric_limits<type>::min() / 2 + std::numeric_limits<type>::epsilon() );
    }
};

好消息是,有一个解决方案不涉及改变项目构建规则,使优化生效,并有效地相同。

看来罪魁祸首实际上是t.t1 = t.t2 = ...子句。为什么这样的赋值会触发链接器崩溃,而等价表达式不会,这是微软要深入研究的一个谜,但实际上,解决方案似乎会导致代码看起来稍微好一些。

这将只在Release和VS 2015中隔离#pragma

#if ( NDEBUG && _MSC_VER == 1900 )
#pragma optimize ("d", on) // same as /Od
#endif

只是为了补充使用基于c++的变通方法的答案,这里是一个使用CMake的CHECK_CXX_SOURCE_COMPILES()宏的基于CMake的实现示例:

cmake_minimum_required(VERSION 3.2)
include(CheckCXXSourceCompiles)
project(TestCompile CXX)
if (MSVC AND MSVC_VERSION GREATER 1899)
    set(CMAKE_TRY_COMPILE_CONFIGURATION "Release")
    CHECK_CXX_SOURCE_COMPILES(
        "
        #include <limits>
        template<typename T>
        struct one
        {
            typedef T type;
            T t1, t2;
        };
        template<typename T>
        struct two
        {
            T t;
            void func()
            {
                typedef typename T::type type;
                t.t1 = t.t2 = std::numeric_limits<type>::min() / 2 + std::numeric_limits<type>::epsilon();
            }
        };
        void main()
        {
            two<one<int> > obj;
            obj.func();
        }
        "
        CAN_LINK_MY_TEMPLATES_EXAMPLE
    )
    if (NOT CAN_LINK_MY_TEMPLATES_EXAMPLE)
        add_compile_options("/Od")
    endif()
endif()

我认为你不必改变特定构建配置的标志,用add_compile_options("/Od")一般禁用它就足够了。

如果你想在每个配置中这样做,你可以替换/删除CMAKE_CXX_FLAGS_...变量中的/O2标志(如这里或这里)。