Как убедить MSVC 10 в том, что виртуальная функция не кидает исключений?
От: Maxim Yurchuk  
Дата: 05.02.15 11:53
Оценка:
Добрый день,

вопрос в следующем: есть некоторая архитектура похожая на COM. В ней есть базовый интерфейс, от которого наследуются другие интерфейсы:


struct IBaseInterface
{
    virtual uint32_t AddRef() = 0;
    virtual uint32_t Release() = 0;
    virtual int      QueryInterface(uint32_t id, void** object) = 0;
};


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

inline uint32_t GetReferenceCount(IBaseInterface *obj)
{
    uint32_t ref = obj->AddRef();
    obj->Release();
    return ref - 1;
}


Компилятор захочет вставить в место инлайна этой функции SEH-фрейм, по причине того, что он предполагает что функции AddRef и Release могут кидать исключения. Особенно досадно это видеть в деструкторах, которые делают Release для интерфейсов. Добавив в пару широко используемых функций (например в smart pointer, который делает в конструкторе AddRef, а в деструкторе Release), спецификатор __declspec(nothrow) (этот спецификатор убирает SEH-фрейм в данной функции и считает, что эта функция не кидает исключения) я получил существенный выигрыш по объему сгенерированного кода (модуль размером 2.7 мб уменьшился на 6%). Но писать этот спецификатор по месту использования не слишком рационально, хотелось бы пометить сами интерфейсы как не кидающие.

Попробовал несколько способов достижения желаемого эффекта, но они меня все не удовлетворили. Перечислю их ниже (для простоты я оставил только метод Release):

  1. Этот код не приносит ожидаемого эффекта. MSVC все равно думает, что этот метод может кинуть.
    struct IBaseInterface
    {
        __declspec(nothrow) virtual uint32_t Release() = 0;
    };

  2. Этот код аналогичен предыдущему и необходимый эффект не достигается.
    struct IBaseInterface
    {
        virtual uint32_t Release() throw() = 0;
    };

  3. Т.к. MSVC считает (если компилировать с флагом /EHsc), что функции, объявленные как extern "C" никогда не кидают, была сделана попытка поместить интерфейс в extern "C". Но требуемого поведения, все равно не получилось достигнуть.
    extern "C" 
    {
    struct IBaseInterface
    {
        virtual uint32_t Release() = 0;
    };
    }

Возможно, кто-нибудь знает способ как можно заставить MSVC 10 думать, что виртуальный метод не кидает исключений? Тогда бы он догадался не делать лишние SEH-фреймы и стал бы лучше генерировать код.

Спасибо.
Re: Как убедить MSVC 10 в том, что виртуальная функция не ки
От: Pavel Dvorkin Россия  
Дата: 05.02.15 16:28
Оценка: +1
Здравствуйте, Maxim Yurchuk, Вы писали:

__declspec(nothrow) говорит о том, что ты обещаешь не выбрасывать исключения с помощью throw. Но это синхронные исключения, а гарантировать, что не будет SEH-исключений, ты не можешь. Деление на 0 — и пожалуйста. И что потом делать ?
With best regards
Pavel Dvorkin
Отредактировано 05.02.2015 16:29 Pavel Dvorkin . Предыдущая версия . Еще …
Отредактировано 05.02.2015 16:29 Pavel Dvorkin . Предыдущая версия .
Re[2]: Как убедить MSVC 10 в том, что виртуальная функция не ки
От: Andrew S Россия http://alchemy-lab.com
Дата: 05.02.15 17:15
Оценка:
PD>__declspec(nothrow) говорит о том, что ты обещаешь не выбрасывать исключения с помощью throw. Но это синхронные исключения, а гарантировать, что не будет SEH-исключений, ты не можешь. Деление на 0 — и пожалуйста. И что потом делать ?

Очевидно, падать, т.к. опции компиляции Ehsc. А разве кто-то ожидал другого?
С т.з. VC __declspec(nothrow) говорит не о том, что функция не будет выбрасывать исключений для внешнего кода (что можно проверить, указав это для функции и убедиться, что код обработки исключений из внешнего кода не исчезает), а о том, что внутри функции не выбрасываются исключения, если иное не следует явно. И внутри функции, промаркированной как throw() как раз код обработки исключений в этом случае отсутствует. gcc же, напротив, по спецификатору noexcept считает во внешнем коде, что вызываемая функция не кидает исключений и убирает обработчик из внешнего кода.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[3]: Как убедить MSVC 10 в том, что виртуальная функция не ки
От: Pavel Dvorkin Россия  
Дата: 05.02.15 17:26
Оценка:
Здравствуйте, Andrew S, Вы писали:

AS>С т.з. VC __declspec(nothrow) говорит не о том, что функция не будет выбрасывать исключений для внешнего кода (что можно проверить, указав это для функции и убедиться, что код обработки исключений из внешнего кода не исчезает), а о том, что внутри функции не выбрасываются исключения, если иное не следует явно. И внутри функции, промаркированной как throw() как раз код обработки исключений в этом случае отсутствует.


Да, ты прав. Но это не отменяет того, что я сказал — выбрасывать SEH исключения она все равно может.

>gcc же, напротив, по спецификатору noexcept считает во внешнем коде, что вызываемая функция не кидает исключений и убирает обработчик из внешнего кода.
With best regards
Pavel Dvorkin
Re[4]: Как убедить MSVC 10 в том, что виртуальная функция не ки
От: Andrew S Россия http://alchemy-lab.com
Дата: 05.02.15 17:48
Оценка:
AS>>С т.з. VC __declspec(nothrow) говорит не о том, что функция не будет выбрасывать исключений для внешнего кода (что можно проверить, указав это для функции и убедиться, что код обработки исключений из внешнего кода не исчезает), а о том, что внутри функции не выбрасываются исключения, если иное не следует явно. И внутри функции, промаркированной как throw() как раз код обработки исключений в этом случае отсутствует.

PD>Да, ты прав. Но это не отменяет того, что я сказал — выбрасывать SEH исключения она все равно может.


Так вопрос то в другом — как убедить компилятор, что C++ исключений быть не может и SEH фрейм, который он делает только для этого, тут не нужен.

>>gcc же, напротив, по спецификатору noexcept считает во внешнем коде, что вызываемая функция не кидает исключений и убирает обработчик из внешнего кода.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re: Как убедить MSVC 10 в том, что виртуальная функция не кидает исключений?
От: Pretender  
Дата: 06.02.15 09:01
Оценка:
На правах гипотезы — возможно, визуальник защищается не от того, что функция кинет, а от разыменовывания нулевого указателя. Может, если там будет, например, ссылка — ему будет легче?
Или __assume (obj != 0). Только обратите внимание что __assume с неправильным условием крайне опасен.
Re[2]: Как убедить MSVC 10 в том, что виртуальная функция не кидает исключений?
От: Maxim Yurchuk  
Дата: 06.02.15 11:23
Оценка:
Здравствуйте, Pretender, Вы писали:

P>На правах гипотезы — возможно, визуальник защищается не от того, что функция кинет, а от разыменовывания нулевого указателя. Может, если там будет, например, ссылка — ему будет легче?

Нет, он защищается не от разыменования нулевого указателя. И защищаться он от этого не должен: это undefined behaviour.

P>Или __assume (obj != 0). Только обратите внимание что __assume с неправильным условием крайне опасен.

Это не помогает. Но, спасибо, что сказали о наличии __assume, не знал об этом.
Re[5]: Как убедить MSVC 10 в том, что виртуальная функция не ки
От: Andrew S Россия http://alchemy-lab.com
Дата: 06.02.15 17:01
Оценка:
AS>>>С т.з. VC __declspec(nothrow) говорит не о том, что функция не будет выбрасывать исключений для внешнего кода (что можно проверить, указав это для функции и убедиться, что код обработки исключений из внешнего кода не исчезает), а о том, что внутри функции не выбрасываются исключения, если иное не следует явно. И внутри функции, промаркированной как throw() как раз код обработки исключений в этом случае отсутствует.

PD>>Да, ты прав. Но это не отменяет того, что я сказал — выбрасывать SEH исключения она все равно может.


AS>Так вопрос то в другом — как убедить компилятор, что C++ исключений быть не может и SEH фрейм, который он делает только для этого, тут не нужен.


Поправлюсь — VC на самом деле учитывает и внешнй скоп для throw() функций. Но только не для виртуальных. Для них он делает seh frame вне зависимости от их спецификации исключений/наличия прагмы. Похоже, ms не очень заботится о размере COM-овского и ему подобного кода .
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re: Похоже нашел workaround
От: Maxim Yurchuk  
Дата: 07.02.15 18:59
Оценка: 4 (1)
Похоже я нашел один workaround, который обладает обратной совместимостью (в моем случае) и позволяет генерировать код, который мне хочется.

Достаточно заменить интерфейс

struct IInterface
{
    virtual void Foo() = 0;
};


На следующий код:

struct IInterfaceVirtual
{
    virtual void Foo() = 0;
};

struct IInterface : protected IInterfaceVirtual
{
    __declspec(nothrow) void Foo() volatile 
    {
        return static_cast<IInterfaceVirtual*>(const_cast<IInterface*>(this))->Foo();
    }
};


volatile нужен для того, чтобы функция из IInterface была не виртуальной (скорее всего, можно заменить volatile на const). В этом случае компилятор поймет аттрибут __declspec(nothrow) и сделает как надо.

На первый взгляд решение выглядит более-менее. Может быть, я что-нибудь не учел?
Re[2]: Похоже нашел workaround
От: sizeof_void Россия  
Дата: 09.02.15 13:33
Оценка:
Здравствуйте, Maxim Yurchuk, Вы писали:

MY>Похоже я нашел один workaround, который обладает обратной совместимостью (в моем случае) и позволяет генерировать код, который мне хочется.


MY>Достаточно заменить интерфейс


MY>
MY>struct IInterface
MY>{
MY>    virtual void Foo() = 0;
MY>};
MY>


MY>На следующий код:


MY>
MY>struct IInterfaceVirtual
MY>{
MY>    virtual void Foo() = 0;
MY>};

MY>struct IInterface : protected IInterfaceVirtual
MY>{
MY>    __declspec(nothrow) void Foo() volatile 
MY>    {
MY>        return static_cast<IInterfaceVirtual*>(const_cast<IInterface*>(this))->Foo();
MY>    }
MY>};
MY>


MY>volatile нужен для того, чтобы функция из IInterface была не виртуальной (скорее всего, можно заменить volatile на const). В этом случае компилятор поймет аттрибут __declspec(nothrow) и сделает как надо.


MY>На первый взгляд решение выглядит более-менее. Может быть, я что-нибудь не учел?


У тебя тут получаются 2 разные функции Foo(). Одна не-'volatile' и виртуальная, другая 'volatile' и не виртуальная.
Получается, что по указателю на баз.класс ты не вызовешь свою производную функцию.
Тебе разве такое поведение нужно?
"May the Force be with us all!" (c)
Re[2]: Похоже нашел workaround
От: Alexander G Украина  
Дата: 09.02.15 14:10
Оценка:
Здравствуйте, Maxim Yurchuk, Вы писали:

MY>volatile нужен для того, чтобы функция из IInterface была не виртуальной (скорее всего, можно заменить volatile на const). В этом случае компилятор поймет аттрибут __declspec(nothrow) и сделает как надо.


MY>На первый взгляд решение выглядит более-менее. Может быть, я что-нибудь не учел?


я бы остерёгся volatile, может лучше добавить лишний задефолченый параметр?
Русский военный корабль идёт ко дну!
Re[3]: Похоже нашел workaround
От: Maxim Yurchuk  
Дата: 09.02.15 20:09
Оценка:
Здравствуйте, sizeof_void, Вы писали:

_>У тебя тут получаются 2 разные функции Foo(). Одна не-'volatile' и виртуальная, другая 'volatile' и не виртуальная.

_>Получается, что по указателю на баз.класс ты не вызовешь свою производную функцию.
_>Тебе разве такое поведение нужно?

Через указатель на IInterface вызывается сначала не виртуальная функция, которая, в свою очередь, вызывает виртуальную функцию, которая реализована в производном классе (не в интерфейсном). За счет того, что сначала вызывается не виртуальная функция с __declspec(nothrow) компилятор понимает, что интерфейс не кидает исключений.
Re[3]: Похоже нашел workaround
От: Maxim Yurchuk  
Дата: 09.02.15 20:11
Оценка:
Здравствуйте, Alexander G, Вы писали:

AG>я бы остерёгся volatile, может лучше добавить лишний задефолченый параметр?


Да, наверное, это тоже подойдет. Cпасибо за вариант.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.