Здравствуйте, igna, Вы писали:
I>На основании чего VC9 определяет функции-члены класса std::exception без спецификаций исключений, ведь стандарт требует их:
1) На основании кривых рук?
2) На основании того, что
— компиляцию это не сломает,
— авторы реализации STL и без того поклялись не кидать исключение из конструктора std::exception,
— какая разница, рухнет программа предсказуемо, с terminate(), если они всё-таки нарушат клятву, или возникнет какое-то неопределённое поведение
— а если разницы нет, то зачем платить за проверку — неявный catch(...){terminate();} в определении функций, помеченных как throw()
То есть, это такая оптимизация.
Здравствуйте, Alexander G, Вы писали:
AG>Ну, при желании можно написать код, который об это сломается. AG>Реально, можно в переопределённом в what забыть про throw, и сломается уже на другом компиляторе.
Здравствуйте, Кодт, Вы писали:
К>Что-то туплю, как можно сломаться об это?
1. Сломать на других компиляторах, просмотрев из-за отсутсвия контроля со сотроны MSVC
class SomeException : public std::exception
{
public:
char const * what(); // будет работать в MSVC, но сломается на других конпиляторах
}
2. Думаю, теоретически можно написать код, который бы был правлиьным по стандарту, но сломался бы в MSVC.
Была такая идея, но MSVC почему-то это компилирует:
AG>class SomeException : public std::exception
AG>{
AG>public:
AG> char const * what(); // будет работать в MSVC, но сломается на других конпиляторах
AG>}
Здесь между прочим ошибка, what должна быть константной, иначе она скрывает exception::what, причем VC9 предупреждения не выдает. Но это так, по ходу дела, я собственно о другом хотел написать, а именно, что даже если добавить спецификацию исключения, аналогичная проблема, то есть то, что будет работать в MSVC, но сломается на других компиляторах, может возникнуть, если класс будет иметь члены с деструктором без спецификации исключения или со спецификацией исключения отличной от throw(), а деструктор будет генерироваться компилятором. Тогда спецификация исключений сгенерированного деструктора будет несовместима со спецификацией исключений деструктора std::exception:
class SomeException : public std::exception
{
std::string what_;
public:
char const * what() const throw();
};
"ComeauTest.c", line 4: error: exception specification for implicitly declared
virtual function "SomeException::~SomeException" is incompatible
with that of overridden function "std::exception::~exception"
class SomeException : public std::exception
^
То есть нужно определить деструктор самому:
class SomeException : public std::exception
{
std::string what_;
public:
~SomeException() throw() {}
char const * what() const throw();
};
Здравствуйте, igna, Вы писали:
I>Здесь между прочим ошибка, what должна быть константной, иначе она скрывает exception::what, причем VC9 предупреждения не выдает.
// C4263.cpp
// compile with: /W4#pragma warning(default:4263)
#pragma warning(default:4264)
class B {
public:
virtual void func();
};
class D : public B {
void func(int); // C4263
};
int main() {
}
И получаем 37 предупреждений, относящихся в основном к стандартным заголовочным файлам:
------ Build started: Project: test, Configuration: Debug Win32 ------
Compiling...
test.cpp
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(114) : warning C4820: '_wfinddata64i32_t' : '4' bytes padding added after data member '_wfinddata64i32_t::attrib'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(119) : warning C4820: '_wfinddata64i32_t' : '4' bytes padding added after data member '_wfinddata64i32_t::name'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(123) : warning C4820: '_wfinddata64_t' : '4' bytes padding added after data member '_wfinddata64_t::attrib'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(493) : warning C4820: '_stat32' : '2' bytes padding added after data member '_stat32::st_gid'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(509) : warning C4820: 'stat' : '2' bytes padding added after data member 'stat::st_gid'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(525) : warning C4820: '_stat32i64' : '2' bytes padding added after data member '_stat32i64::st_gid'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(526) : warning C4820: '_stat32i64' : '4' bytes padding added after data member '_stat32i64::st_rdev'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(530) : warning C4820: '_stat32i64' : '4' bytes padding added after data member '_stat32i64::st_ctime'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(539) : warning C4820: '_stat64i32' : '2' bytes padding added after data member '_stat64i32::st_gid'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(553) : warning C4820: '_stat64' : '2' bytes padding added after data member '_stat64::st_gid'
c:\programme\microsoft visual studio 9.0\vc\include\wchar.h(554) : warning C4820: '_stat64' : '4' bytes padding added after data member '_stat64::st_rdev'
c:\programme\microsoft visual studio 9.0\vc\include\typeinfo(58) : warning C4820: 'type_info' : '3' bytes padding added after data member 'type_info::_m_d_name'
c:\programme\microsoft visual studio 9.0\vc\include\xlocale(1865) : warning C4548: expression before comma has no effect; expected expression with side-effect
c:\programme\microsoft visual studio 9.0\vc\include\xlocale(1890) : warning C4548: expression before comma has no effect; expected expression with side-effect
c:\programme\microsoft visual studio 9.0\vc\include\xlocale(2137) : warning C4548: expression before comma has no effect; expected expression with side-effect
c:\programme\microsoft visual studio 9.0\vc\include\xlocale(2172) : warning C4548: expression before comma has no effect; expected expression with side-effect
c:\programme\microsoft visual studio 9.0\vc\include\xlocale(2420) : warning C4548: expression before comma has no effect; expected expression with side-effect
c:\programme\microsoft visual studio 9.0\vc\include\xlocale(2454) : warning C4548: expression before comma has no effect; expected expression with side-effect
c:\dokumente und einstellungen\igor (user)\eigene dateien\visual studio 2008\projects\test\test\test.cpp(9) : warning C4263: 'const char *SomeException::what(void) throw()' : member function does not override any base class virtual member function
c:\dokumente und einstellungen\igor (user)\eigene dateien\visual studio 2008\projects\test\test\test.cpp(10) : warning C4264: 'const char *std::exception::what(void) const' : no override available for virtual member function from base 'std::exception'; function is hidden
c:\programme\microsoft visual studio 9.0\vc\include\exception(201) : see declaration of 'std::exception::what'
c:\programme\microsoft visual studio 9.0\vc\include\exception(95) : see declaration of 'std::exception'
c:\programme\microsoft visual studio 9.0\vc\include\xstring(1566) : warning C4548: expression before comma has no effect; expected expression with side-effect
c:\programme\microsoft visual studio 9.0\vc\include\xstring(1557) : while compiling class template member function 'char &std::basic_string<_Elem,_Traits,_Ax>::operator [](unsigned int)'
with
[
_Elem=char,
_Traits=std::char_traits<char>,
_Ax=std::allocator<char>
]
c:\programme\microsoft visual studio 9.0\vc\include\xstring(2221) : see reference to class template instantiation 'std::basic_string<_Elem,_Traits,_Ax>' being compiled
with
[
_Elem=char,
_Traits=std::char_traits<char>,
_Ax=std::allocator<char>
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::_DebugHeapString::operator std::string(void) const' : function not inlined
c:\programme\microsoft visual studio 9.0\vc\include\xdebug(138) : see declaration of 'std::_DebugHeapString::operator std::string'
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::string std::_Locinfo::_Getname(void) const' : function not inlined
c:\programme\microsoft visual studio 9.0\vc\include\xlocinfo(112) : see declaration of 'std::_Locinfo::_Getname'
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::string std::locale::name(void) const' : function not inlined
c:\programme\microsoft visual studio 9.0\vc\include\xlocale(406) : see declaration of 'std::locale::name'
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::locale std::ios_base::getloc(void) const' : function not inlined
c:\programme\microsoft visual studio 9.0\vc\include\xiosbase(429) : see declaration of 'std::ios_base::getloc'
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::string std::numpunct<_Elem>::do_grouping(void) const' : function not inlined
with
[
_Elem=char
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(173) : see declaration of 'std::numpunct<_Elem>::do_grouping'
with
[
_Elem=char
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::basic_string<_Elem,_Traits,_Ax> std::numpunct<_Elem>::do_falsename(void) const' : function not inlined
with
[
_Elem=char,
_Traits=std::char_traits<char>,
_Ax=std::allocator<char>
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(178) : see declaration of 'std::numpunct<_Elem>::do_falsename'
with
[
_Elem=char
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::basic_string<_Elem,_Traits,_Ax> std::numpunct<_Elem>::do_truename(void) const' : function not inlined
with
[
_Elem=char,
_Traits=std::char_traits<char>,
_Ax=std::allocator<char>
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(183) : see declaration of 'std::numpunct<_Elem>::do_truename'
with
[
_Elem=char
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::string std::numpunct<_Elem>::do_grouping(void) const' : function not inlined
with
[
_Elem=wchar_t
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(173) : see declaration of 'std::numpunct<_Elem>::do_grouping'
with
[
_Elem=wchar_t
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::basic_string<_Elem,_Traits,_Ax> std::numpunct<_Elem>::do_falsename(void) const' : function not inlined
with
[
_Elem=wchar_t,
_Traits=std::char_traits<wchar_t>,
_Ax=std::allocator<wchar_t>
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(178) : see declaration of 'std::numpunct<_Elem>::do_falsename'
with
[
_Elem=wchar_t
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::basic_string<_Elem,_Traits,_Ax> std::numpunct<_Elem>::do_truename(void) const' : function not inlined
with
[
_Elem=wchar_t,
_Traits=std::char_traits<wchar_t>,
_Ax=std::allocator<wchar_t>
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(183) : see declaration of 'std::numpunct<_Elem>::do_truename'
with
[
_Elem=wchar_t
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::basic_string<_Elem,_Traits,_Ax> std::numpunct<_Elem>::falsename(void) const' : function not inlined
with
[
_Elem=char,
_Traits=std::char_traits<char>,
_Ax=std::allocator<char>
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(83) : see declaration of 'std::numpunct<_Elem>::falsename'
with
[
_Elem=char
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::basic_string<_Elem,_Traits,_Ax> std::numpunct<_Elem>::truename(void) const' : function not inlined
with
[
_Elem=char,
_Traits=std::char_traits<char>,
_Ax=std::allocator<char>
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(88) : see declaration of 'std::numpunct<_Elem>::truename'
with
[
_Elem=char
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::basic_string<_Elem,_Traits,_Ax> std::numpunct<_Elem>::falsename(void) const' : function not inlined
with
[
_Elem=wchar_t,
_Traits=std::char_traits<wchar_t>,
_Ax=std::allocator<wchar_t>
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(83) : see declaration of 'std::numpunct<_Elem>::falsename'
with
[
_Elem=wchar_t
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::basic_string<_Elem,_Traits,_Ax> std::numpunct<_Elem>::truename(void) const' : function not inlined
with
[
_Elem=wchar_t,
_Traits=std::char_traits<wchar_t>,
_Ax=std::allocator<wchar_t>
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(88) : see declaration of 'std::numpunct<_Elem>::truename'
with
[
_Elem=wchar_t
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::string std::numpunct<_Elem>::grouping(void) const' : function not inlined
with
[
_Elem=char
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(78) : see declaration of 'std::numpunct<_Elem>::grouping'
with
[
_Elem=char
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(135) : warning C4710: 'std::string std::numpunct<_Elem>::grouping(void) const' : function not inlined
with
[
_Elem=wchar_t
]
c:\programme\microsoft visual studio 9.0\vc\include\xlocnum(78) : see declaration of 'std::numpunct<_Elem>::grouping'
with
[
_Elem=wchar_t
]
Build log was saved at "file://c:\Dokumente und Einstellungen\Igor (user)\Eigene Dateien\Visual Studio 2008\Projects\test\test\Debug\BuildLog.htm"
test — 0 error(s), 37 warning(s)
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========
В последнем варианте приходится как-то различать откуда исключение — из конструктора exception, или нет.
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Здравствуйте, gear nuke, Вы писали:
I>>На основании чего VC9 определяет функции-члены класса std::exception без спецификаций исключений
GN>Для оптимизации. Разницу в сгенерированном коде видно, если указать ключ /d1ESrt.
Этот ключ даже не документирован и вряд ли кем-то используется в продакшн, а без него скорее код с пустой спецификацией исключений будет лучше оптимизирован.
Здравствуйте, Alexander G, Вы писали:
AG>Этот ключ даже не документирован и вряд ли кем-то используется в продакшн
Это не имеет отношения к делу.
AG>а без него скорее код с пустой спецификацией исключений будет лучше оптимизирован.
Какие для этого есть предпосылки, хотя бы в теории?
И я ведь мог написать — мы тоже используем exception без спецификаторов исключений, потому что это дает более оптимальный код. Но я не Саттер, много кто поверит?
Поэтому я воспроизвел "лишний код" самым простым способом. /d1ESrt в данном случае используется для отключения некоторых оптимизаций (на самом деле он еще добавляет таблицы с данными, не связанные с exception(); ).
"Лишний код" в листинге не несет ничего, чем его можно связать с /d1ESrt. Это выполнение конструктора "в как-бы try блоке". Дополнительный SEH фрейм нужен, что бы диспетчер исключений понял, бросил ли конструктор.
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Здравствуйте, gear nuke, Вы писали:
AG>>а без него скорее код с пустой спецификацией исключений будет лучше оптимизирован.
GN>Какие для этого есть предпосылки, хотя бы в теории?
__declspec(nothrow) позволяет компилятору предположить, что функция никогда не бросает исключения.
С /EHs — исключениями это может существенно уменьшить сгенерированный код.
Пустая спецификация исключений, именно пустая (и именно по умолчанию, а не с /d1-ключами) эквивалентна __declspec(nothrow)
Здравствуйте, Alexander G, Вы писали:
AG>__declspec(nothrow) позволяет компилятору предположить, что функция никогда не бросает исключения. AG>С /EHs — исключениями это может существенно уменьшить сгенерированный код.
За счет чего /EHs может уменьшить код мне понятно, хоть ты и не указал. И это не имеет отношения к теме. Компилятор не генериреут некоторые фреймы, т.к. видит поток управления и уверен что там не возникнут исключения. Он может использовать jmp вместо длинной цепочки _CxxThrowException --> __CxxFrameHandler3
Вот примерно такие теоретические умозаключения я и хочу увидеть в подтверждение твоей теории. Без них она совсем ничего не стоит.
AG>Пустая спецификация исключений, именно пустая (и именно по умолчанию, а не с /d1-ключами) эквивалентна __declspec(nothrow)
Я видел код аналогичный приведённому и без /d1ESrt (не надо цепляться к /d1 ключам, это всего лишь ключи для фронтенда С++). Я его не могу привести сейчас, сходу, на минимальном примере. Но в проекте по-больше было видно что общий размер кода увеличивается.
Почему появляется дополнительный код — потому что нужно знать, где было исключение в функции или вне. Теория согласуется с практикой. Я не знаю, почему у MS именно так, но здесь std::exception такой именно по этой причине.
AG>см. http://msdn.microsoft.com/en-us/library/49147z04(VS.71).aspx
Дык сам и смотри:
Using void declspec(nothrow) stdcall f2(); has the advantage that you can use an API definition, such as that illustrated by the #define statement, to easily specify nothrow on a set of functions. The third declaration, void stdcall f3() throw(); is the syntax defined by the C++ standard.
Это одно и тоже. Помедитируй еще над C4290. В библиотечном коде нестандартное и правда удобнее использовать.
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Здравствуйте, Alexander G, Вы писали:
AG>Пустая спецификация исключений, именно пустая (и именно по умолчанию, а не с /d1-ключами) эквивалентна __declspec(nothrow)
Сорри, выше я что-то переборщил с аргументами, ты пишешь тоже самое.
Теперь посмотрим на 2 ситуации:
1. функция без спецификации — бросит, не бросит — диспетчер исключений это не волнует. Он поймает исключение и вызовет подходящий catch, если сможет.
2. функция с throw(). Она как бы не может бросить, так думает автор кода. Но диспетчер исключений не может быть уверен, что автор не ошибся. Он должен различить ситуацию, когда функция все-таки что-то бросила, что бы вызвать unexpected(). Потому такой вариант требует допонительный код. Если компилятор способен проследить поток управления и убедиться что там нет подозрительных мест, он не будет генерировать SEH фреймы и/или изменение exception state в них.
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Здравствуйте, gear nuke, Вы писали:
GN>Сорри, выше я что-то переборщил с аргументами, ты пишешь тоже самое.
GN>Теперь посмотрим на 2 ситуации: GN>1. функция без спецификации — бросит, не бросит — диспетчер исключений это не волнует. Он поймает исключение и вызовет подходящий catch, если сможет.
+
GN>2. функция с throw(). Она как бы не может бросить, так думает автор кода. Но диспетчер исключений не может быть уверен, что автор не ошибся. Он должен различить ситуацию, когда функция все-таки что-то бросила, что бы вызвать unexpected(). Потому такой вариант требует допонительный код. Если компилятор способен проследить поток управления и убедиться что там нет подозрительных мест, он не будет генерировать SEH фреймы и/или изменение exception state в них.
Насколько я понимаю, без хитрых ключей вроде /d1ESrt — даже код, вызывающий unexpected, сгенерирован не будет, иначе не было бы по моей ссылке в самом начале:
This attribute tells the compiler that the declared function and the functions it calls never throw an exception. With the synchronous exception handling model, now the default, the compiler can eliminate the mechanics of tracking the lifetime of certain unwindable objects in such a function, and significantly reduce the code size.
Насколько я помню, Саттер тоже упоминал, что пустая спецификация в MSVC не вызывает unexpected.
It’s actually even a bit worse than that in practice, because it turns out that popular implementations vary in how they actually handle exception specifications. At least one popular C++ compiler (Microsoft’s, up to version 7.x) parses exception specifications but does not actually enforce them, reducing the exception specifications to glorified comments. But, on the other hand, there are legal optimizations a compiler can perform outside a function, and which the Microsoft 7.x compiler does perform, that rely on the ES enforcement being done inside each function -- the idea is that if the function did try to throw something it shouldn’t the internal handler would stop the program and control would never return to the caller, so since control did return to the caller the calling code can assume nothing was thrown and do things like eliminate external try/catch blocks. So on that compiler, because the checking is not done but the legal optimization that relies on it is done, the meaning of “throw()” changes from the standard “check me on this, stop me if I inadvertently throw” to a “trust me on this, assume I’ll never throw and optimize away.” So beware: If you do choose to use even an empty throw-specification, read your compiler’s documentation and check to see what it will really do with it. You might just be surprised. Be aware, drive with care.
Под "internal handler" подразумевается явно написанный обработчик, т.к. про неявный чётко сказано, что его нет.
Здравствуйте, Alexander G, Вы писали:
AG>Насколько я понимаю, без хитрых ключей вроде /d1ESrt — даже код, вызывающий unexpected, сгенерирован не будет
/d1ESrt гененирует только "List of expected exceptions" (см. здесь). Код, вызывающий unexpected (а так же код, проверяющий списки ожидаемых исключений) безусловно находится в CRT и линкуется к каждому приложению (использующему этот рантайм).
AG>, иначе не было бы по моей ссылке в самом начале:
[]
Не вижу там про связь между /EHs и /d1ESrt. Однако общее у этих ключей есть — заставляют компилятор не делать некоторые допущения, отсюда он не может "eliminate" некоторый код. Вот только поэтому я и использовал /d1ESrt, в других случаях для этого пишу volatile.
Вот прицепился то к ключу Это, конечно, совершенно понятно: агрументов в защиту твоей теории нет
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
the idea is that if the function did try to throw something it shouldn’t the internal handler would stop the program and control would never return to the caller
AG>Под "internal handler" подразумевается явно написанный обработчик, т.к. про неявный чётко сказано, что его нет.
Если внимательно изучить исходники диспетчера исключений, то окажется, что под этим понимается внутренний обработчик в CRT, а именно __CxxFrameHandler3 котрый вызывает далее __InternalCxxFrameHandler
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Здравствуйте, gear nuke, Вы писали:
GN>Это, конечно, совершенно понятно: агрументов в защиту твоей теории нет
Хорошо, если не понимаешь цитаты MSDN и Саттера, вот аргумент кодом.
Чтобы не дать компилятору проигнорировать throw() и его отсутствие, разместим вызовы
void cb1(); и void cb2() throw(); в одной единнице трансляции, а реализацию в другой, при этом /LTCG отключим, в остальном — дефолтные параметры сборки релиза.
Итак, 1.cpp
#include"stdafx.h"struct X
{
X() { puts("ctor"); }
~X() { puts("dtor"); }
};
void cb1();
void cb2() throw();
void test1()
{
X x;
cb1();
}
void test2()
{
X x;
cb2();
}
int main()
{
test1();
test2();
}
2.cpp
void cb1() {}
void cb2() throw(){}
Результат:
int main()
{
00401070 push esi
test1();
00401071 call test1 (401010h)
test2();
00401076 mov esi,dword ptr [__imp__puts (4020A0h)]
0040107C push offset string "ctor" (402170h)
00401081 call esi
00401083 call cb2 (4010A0h)
00401088 push offset string "dtor" (402178h)
0040108D call esi
0040108F add esp,8
}
00401092 xor eax,eax
00401094 pop esi
00401095 ret
test2 заинлайнился, и видно, что оптимизатор предположил, что cb2 никогда не бросит.
Из листинга test1 вполне видно, что исключение из cb1 ожидается.
Здравствуйте, Alexander G, Вы писали:
AG>Хорошо, если не понимаешь цитаты MSDN и Саттера
Я их понимаю, просто не так, как тебе хочется. Наверное, потому что я видел реализацию диспетчера исключений и имею представление о его работе, а не увы.
AG>вот аргумент кодом.
Мне, в общем-то, хватит теоретических умозаключений, зачем оно бы так могло бы быть.
А код... посмотрим. Кстати, зачем лишняя сущность struct X? А как на счёт throw() в её *structors? Это повлияет на листинги
AG>
int main()
AG>{
AG>00401070 push esi
AG> test1();
AG>00401071 call test1 (401010h)
AG> test2();
AG>00401076 mov esi,dword ptr [__imp__puts (4020A0h)]
AG>0040107C push offset string "ctor" (402170h)
AG>00401081 call esi
AG>00401083 call cb2 (4010A0h)
AG>00401088 push offset string "dtor" (402178h)
AG>0040108D call esi
AG>0040108F add esp,8
AG>}
AG>00401092 xor eax,eax
AG>00401094 pop esi
AG>00401095 ret
AG>
AG>test2 заинлайнился
Причина: SEH фрейм мешает инлайну, в test2 он не устанавливается, поэтому инлайн возможен.
AG>, и видно, что оптимизатор предположил, что cb2 никогда не бросит.
Откуда видно? Мне видно, что диспетчер исключений вызовет unexpected() когда cb2() бросит. Такова его логика — не будет найден хендлер, будет вызван unexpected() за счёт того, что CRT вызвает SetUnhandledExceptionFilter. Если функция не ловит исключение, это делает вызывающий код — оптимизатор здесь его не видит.
AG>Из листинга test1 вполне видно, что исключение из cb1 ожидается. AG>
Тут много мусора. security cookie можно отключить /GS- (вообще, впринципе и 2.cpp не обязателен, листинг все равно будет)
Что же здесь видно. В выделенной строке сохраняется exception state, признак, что объект x сконструирован и при возникновении исключения нужно вызвать его деструктор. Хотя, про деструктор станет видно, если смотреть цепочку
Что касается неразрушения объекта в test2 — это как-то противоречит Стандарту? Попробуй завернуть внутренности testX, или main в try блок, деструкторы будут добавлены в таблицу раскрутки.
AG>Вывод 1: пустой throw() привёл к оптимизации.
А не "дефолтные параметры сборки релиза" привели ли к оптимизации?
AG>Включаем /d1ESrt
AG>Всё остаётся так же, только cb2 отделилась от cb1 и изменилась на:
AG>
AG>Вывод 2: с /d1ESrt пустой throw приводит к пессимизации
Да я уже несколько постов пишу, что /d1ESrt использован мною для пессимизации А ты как хотел, включить оптимизацию, и трактовать её заслуги как последствия (не)использования спецификаторов?
В моём варианте, как раз оптимизация полностью отключена. Поэтому становится видно влияние других, определяющих факторов, а именно того, что написано в исходном тексте.
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Лишняя сущность struct X как раз нужна для того, чтобы показать оптимизацию для cb2.
У неё деструктор вызывается не в раскрутке стека, а как обычный вызов функции, и если cb2 таки бросит исключение, то dtor не вызовется.
GN>А как на счёт throw() в её *structors? Это повлияет на листинги
В названных условиях — никак не повлияет, код один в один, даже адреса те же.
AG>>
int main()
AG>>{
AG>>00401070 push esi
AG>> test1();
AG>>00401071 call test1 (401010h)
AG>> test2();
AG>>00401076 mov esi,dword ptr [__imp__puts (4020A0h)]
AG>>0040107C push offset string "ctor" (402170h)
AG>>00401081 call esi
AG>>00401083 call cb2 (4010A0h)
AG>>00401088 push offset string "dtor" (402178h)
AG>>0040108D call esi
AG>>0040108F add esp,8
AG>>}
AG>>00401092 xor eax,eax
AG>>00401094 pop esi
AG>>00401095 ret
AG>>
AG>>test2 заинлайнился
GN>Причина: SEH фрейм мешает инлайну, в test2 он не устанавливается, поэтому инлайн возможен.
Да. SEH фрейм стал не нужен благодаря throw(), т.е. throw() это таки оптимизация.
AG>>, и видно, что оптимизатор предположил, что cb2 никогда не бросит.
GN>Откуда видно?
Деструктор после cb2 вызывается как обычная функция.
GN>Что касается неразрушения объекта в test2 — это как-то противоречит Стандарту? Попробуй завернуть внутренности testX, или main в try блок, деструкторы будут добавлены в таблицу раскрутки.
Здравствуйте, Alexander G, Вы писали:
AG>Лишняя сущность struct X как раз нужна для того, чтобы показать оптимизацию для cb2. AG>У неё деструктор вызывается не в раскрутке стека, а как обычный вызов функции, и если cb2 таки бросит исключение, то dtor не вызовется.
Я могу из этого сделать вывод, что невызов деструктора противоречит 15.5.2. Как сделать выводы об оптимизации не знаю (однако, ближе к концу поста, надеюсь расставить всё по своим местам).
GN>>А как на счёт throw() в её *structors? Это повлияет на листинги AG>В названных условиях — никак не повлияет, код один в один, даже адреса те же.
Так я и намекаю, что условия тепличные, а взгляд слишком поверхностный.
В test1() зарегистрировано 2 "раскрутчика стека". При исключении, в зависимости от значения exception state, будет вызван либо __unwindfunclet$?test1@@YGXXZ$0, либо оба обработчика.
0й ehstate устанавливается перед вызовом конструктора X(), а 1й — перед вызовом cb1().
Ключевой момент: упомянутые функции имеют разные спецификаторы исключений
2й вариант без throw() в X. В test1() изменилось лишь разрушение объектов (и кстати видно, что мой компилятор по-старее, не выбрасывает установку -1)
Зато как изменилась test2().
Видно, что зарегистрирован деструктор только одного из объектов — x. Почему так, откуда компилятор "ждёт" исключение? Не надо торопиться с ответом, для него недостаточно исходных данных. Что бы стало достаточно, нужно добавить 3й объект типа X:
Вот только теперь вопросов не остаётся, похоже, что компилятор "не ждёт" исключения из cb2(). Я не знаю, как можно было сделать такой вывод на основании оригинального кода, разве что угадать!
Так, выходит я сдался? Отнюдь, эта долгая прилюдия лишь что бы показать, что не всё так просто, как хотелось бы:
AG>>>, и видно, что оптимизатор предположил, что cb2 никогда не бросит.
GN>>Откуда видно? AG>Деструктор после cb2 вызывается как обычная функция.
Ничего не доказывает этот деструктор. Да, читай выше — я даже как бы показал, что компилятор не ждет исключения. Почему? А потому что смотрим внимательно на пример:
И деструкторы при этом будут вызваны как обычные функции, если enexpected() "вернёт" правильное исключение, никаких нарушений 15.5.2!
Почему же отсутствие throw() так меняет поведение? Потому что в этом случае у функции нет нужды проверять, не выкинула ли она не то исключение. Очевидно, что исключение будет ловиться вызывающим кодом, что и видно на примерах.
GN>>Причина: SEH фрейм мешает инлайну, в test2 он не устанавливается, поэтому инлайн возможен.
AG>Да. SEH фрейм стал не нужен благодаря throw(), т.е. throw() это таки оптимизация.
SEH фрейм никуда не исчез, он переехал из test2() в cb2(). При отсутствии /d1ESrt смог его "оптимизировать" (выкинуть).
На самом деле, разнеся объявления и реализацию по разным единицам трансляции, был поставлен не тот эксперимент.
В конкретном случае с std::exception (о котором и был мой исходный пост) компилятор видит определение. Если бы было указано throw(), ему пришлось бы делать оптимизацию — "поднимать" установку SEH фрейма в вызывающие функции, и после анализа графа вызовов выкидавать его совсем. Часто он справляется, кроме сложных случаев (/d1ESrt). Что бы упростить ему жизнь, разработчики отказались от спецификатора.
GN>>Что касается неразрушения объекта в test2 — это как-то противоречит Стандарту? Попробуй завернуть внутренности testX, или main в try блок, деструкторы будут добавлены в таблицу раскрутки.
AG>Нет, нифига. Сам попробуй.
Спасибо. На ковыряние C++ EH MSVC я и без того потратил не менее 200 часов
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
GN>Ничего не доказывает этот деструктор. Да, читай выше — я даже как бы показал, что компилятор не ждет исключения. Почему? А потому что смотрим внимательно на пример: GN>Исключение перехватывается внутри cb2()! GN>И деструкторы при этом будут вызваны как обычные функции, если enexpected() "вернёт" правильное исключение, никаких нарушений 15.5.2!
Ну, так это с /d1ESrt , а не в реальной жизни.
GN>Почему же отсутствие throw() так меняет поведение? Потому что в этом случае у функции нет нужды проверять, не выкинула ли она не то исключение. Очевидно, что исключение будет ловиться вызывающим кодом, что и видно на примерах.
GN>>>Причина: SEH фрейм мешает инлайну, в test2 он не устанавливается, поэтому инлайн возможен.
AG>>Да. SEH фрейм стал не нужен благодаря throw(), т.е. throw() это таки оптимизация.
GN>SEH фрейм никуда не исчез, он переехал из test2() в cb2(). При отсутствии /d1ESrt смог его "оптимизировать" (выкинуть).
Этот ключ даже не документирован и вряд ли кем-то используется в продакшн, а без него скорее код с пустой спецификацией исключений будет лучше оптимизирован.
GN>На самом деле, разнеся объявления и реализацию по разным единицам трансляции, был поставлен не тот эксперимент.
GN>В конкретном случае с std::exception (о котором и был мой исходный пост) компилятор видит определение.
То, что компилятор видит определение влияет на оптимизацию следующим образом: компилятор может и без throw() понять, что функция никогда не бросит.
GN>Если бы было указано throw(), ему пришлось бы делать оптимизацию — "поднимать" установку SEH фрейма в вызывающие функции, и после анализа графа вызовов выкидавать его совсем.
Что значит пришлось бы поднимать установку SEH фрейма в вызывающие функции? В случае throw() компилятор просто всегда верит, что функция не бросит исключение.
GN>Спасибо. На ковыряние C++ EH MSVC я и без того потратил не менее 200 часов
Я не предлагал ковырять, я просто предлагал проверить, что никакой terminate в описаных условиях не вызывается.
Здравствуйте, Alexander G, Вы писали:
GN>>И деструкторы при этом будут вызваны как обычные функции, если enexpected() "вернёт" правильное исключение, никаких нарушений 15.5.2! AG>Ну, так это с /d1ESrt , а не в реальной жизни.
В "реальной жизни" описанная ситуация невозможна, спецификаторы исключений якобы не поддержаны, так что опять нет нарушения 15.5.2.
AG>т.е. здесь
Этот ключ даже не документирован и вряд ли кем-то используется в продакшн, а без него скорее код с пустой спецификацией исключений будет лучше оптимизирован.
Я наконец-то понял, ты пытаешься рассуждать об общем случае? А я отвечал на вопрос АТ!
GN>>В конкретном случае с std::exception (о котором и был мой исходный пост) компилятор видит определение.
AG>То, что компилятор видит определение влияет на оптимизацию следующим образом: компилятор может и без throw() понять, что функция никогда не бросит.
Разумеется, за исключением нюанса — не должна бросить по контракту. Но у компилятора есть некоторая "глубина анализа", дальше которой он не видит. То есть когда речь идёт о длинных цепочках функций, вызывающих одна другую, оптимизатор может не потянуть.
GN>>Если бы было указано throw(), ему пришлось бы делать оптимизацию — "поднимать" установку SEH фрейма в вызывающие функции, и после анализа графа вызовов выкидавать его совсем.
AG>Что значит пришлось бы поднимать установку SEH фрейма в вызывающие функции?
Я под этим понимаю — смотреть из каких функций вызывается данная, и проверять возможность установить SEH в них, а не в данной. Или заменить бросание исключения безусловным переходом.
AG>Я не предлагал ковырять, я просто предлагал проверить, что никакой terminate в описаных условиях не вызывается.
Не вижу связи между terminate() и оптимизацией std::exception. Я предлагал изучить больше примеров перед попыткой делать выводы.
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Этот ключ даже не документирован и вряд ли кем-то используется в продакшн, а без него скорее код с пустой спецификацией исключений будет лучше оптимизирован.
GN>Я наконец-то понял, ты пытаешься рассуждать об общем случае? А я отвечал на вопрос АТ!
Какой оптимизиции может помочь отсутсвие throw() ?
GN>Разумеется, за исключением нюанса — не должна бросить по контракту. Но у компилятора есть некоторая "глубина анализа", дальше которой он не видит. То есть когда речь идёт о длинных цепочках функций, вызывающих одна другую, оптимизатор может не потянуть.
Может протянуть, может не протянуть, в любом случае, насколько я вижу, хуже от пустого throw() (с /EHs , без /d1... ) не будет.
AG>>Что значит пришлось бы поднимать установку SEH фрейма в вызывающие функции?
GN>Я под этим понимаю — смотреть из каких функций вызывается данная, и проверять возможность установить SEH в них, а не в данной. Или заменить бросание исключения безусловным переходом.
Можно реальный пример, когда SEH фрейм ставится снаружи функции с throw()-спецификацией ?
же есть пример.
AG>Может протянуть, может не протянуть, в любом случае, насколько я вижу, хуже от пустого throw() (с /EHs , без /d1... ) не будет.
Не-не, погоди Давай переформулируем твое предложение как следует. Вынесем необходимые условия из скобочек:
"С ключем /EHs и без /d1ESrt, в тех случаях, которые ты видел, присутсвие throw() не ухудьшает ситуацию".
А теперь пожалуйста — применяй индукцию для обобщения Я, впрочем, не спорю, что где-то так и есть.
В случае с std::exception это позволяет получить оптимальный код при всех возможных вариантах ключей. С /d1ESrt он лучше, без — по крайней мере, не хуже.
Вот кстати из libcomo, и полагаю везде так:
// This header exists solely for portability. Normally it just includes
// the header <exception>.
// The header <exception> contains low-level functions that interact
// with a compiler's exception-handling mechanism. It is assumed to
// be supplied with the compiler, rather than with the library, because
// it is inherently tied very closely to the compiler itself.
и это пишут неплохо разбирающиеся в вопросе люди, они почему-то считают, что если так сделано — значит так надо. Можно поискать объяснения, как это делаю я (сначала -теоретические предпосылки, потом ищем подтверждения на практике). Можно говорить, что это плохо, и лучше по-другому и показывать что в другом случае может быть лучше по-другому, вместо хоть какого-то обоснование в теории.
Может оно надо, что бы результирующий код получался идентичным независимо от ключей компиляции? (libc то собрана с определенными). Вот тут я соглашусь, что с оптимизацией поторопился как с первопричиной. А пока других кандидатов на причины нет — буду придерживаться своей теории.
GN>>Я под этим понимаю — смотреть из каких функций вызывается данная, и проверять возможность установить SEH в них, а не в данной. Или заменить бросание исключения безусловным переходом.
AG>Можно реальный пример, когда SEH фрейм ставится снаружи функции с throw()-спецификацией ?
Я видимо неясно выражаюсь, иначе почему выкидываешь половину контекста? "и после анализа графа вызовов выкидавать его совсем".
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
GN>В случае с std::exception это позволяет получить оптимальный код при всех возможных вариантах ключей. С /d1ESrt он лучше, без — по крайней мере, не хуже.
Как с — не важно. Без — не факт, что не хуже, но никогда не лучше.
GN>Вот кстати из libcomo, и полагаю везде так: GN>
GN>// This header exists solely for portability. Normally it just includes
GN>// the header <exception>.
GN>// The header <exception> contains low-level functions that interact
GN>// with a compiler's exception-handling mechanism. It is assumed to
GN>// be supplied with the compiler, rather than with the library, because
GN>// it is inherently tied very closely to the compiler itself.
GN>
и это пишут неплохо разбирающиеся в вопросе люди, они почему-то считают, что если так сделано — значит так надо.
То, что следует использовать стандартную реализацию, это очевидно, но из этого нельзя сджелать никаких выводов, почему в стандартнй реализации сделано так или иначе.
GN>Можно поискать объяснения, как это делаю я (сначала -теоретические предпосылки, потом ищем подтверждения на практике).
Теоретические предпосылки для оптимизации при наличии throw() просты: компилятор получает дополнительную информацию, при этом не обеспечивает никаких дополнительных гарантий. Соответственно он может или воспользоваться возмоностью для оптимизации, или нет, но испортить не может. То что throw() это __declspec(nothrow), а __delcpsec(nothrow) служит для оптимизации — это в MSDN сказано.
GN>Может оно надо, что бы результирующий код получался идентичным независимо от ключей компиляции? (libc то собрана с определенными). Вот тут я соглашусь, что с оптимизацией поторопился как с первопричиной. А пока других кандидатов на причины нет — буду придерживаться своей теории.
Оптимизация не только не первопричина, но и не причина вообще.
У меня есть более правдоподобные версии.
1. Т.к. спецификация исключений не поддерживается, решили не требовать от разработчиков их писать в наследниках и не указывать их в стандартной библиотеке и её документации.
2. Так сложилось исторически: в VC8 (я на самом деле приводил листинги VC8) там тоже нет спецификаций исключений. Видимо по какой-либо причние их изначально не указали, в последствии не стали добавлять, чтобы не ломать совместимость со старым кодом.
GN>>>Я под этим понимаю — смотреть из каких функций вызывается данная, и проверять возможность установить SEH в них, а не в данной. Или заменить бросание исключения безусловным переходом.
AG>>Можно реальный пример, когда SEH фрейм ставится снаружи функции с throw()-спецификацией ?
GN>Я видимо неясно выражаюсь, иначе почему выкидываешь половину контекста? "и после анализа графа вызовов выкидавать его совсем".
Т.е. из-за throw() происходит такая штука, как "поднятие" обработчика исключений в вызывающую функцию, при этом поднянтый обработчик всегда выбрасывается? Не верю. Я считаю, что всё проще. Обработчик, который якобы поднимается, никогда не генерируется вообще. Я поверю в наличие "поднятого" ообработчика только кода увижу ситуацию, когда он не выбрасывается.
Здравствуйте, Alexander G, Вы писали:
AG>Как с — не важно.
Почему считаешь не важным /d1ESrt? Потому что не используешь сам. Разработчики компилятора не могут руководствоваться такими соображениями. Им необходимо учесть все варианты.
AG>То, что следует использовать стандартную реализацию, это очевидно, но из этого нельзя сджелать никаких выводов, почему в стандартнй реализации сделано так или иначе.
Из этого следует вывод, что разработчикам виднее, я других и не делал.
AG>Теоретические предпосылки для оптимизации при наличии throw() просты: компилятор получает дополнительную информацию, при этом не обеспечивает никаких дополнительных гарантий. Соответственно он может или воспользоваться возмоностью для оптимизации, или нет, но испортить не может. То что throw() это __declspec(nothrow), а __delcpsec(nothrow) служит для оптимизации — это в MSDN сказано.
Да, там сказано что это лучше в одном из случаев (/EHs). При этом там сказано вот что еще:
This attribute tells the compiler that the declared function and the functions it calls never throw an exception.
то есть речь о функциях без определения. std::exception не тот случай, компилятор определение видит, чем ему помогает спецификатор?
AG>У меня есть более правдоподобные версии.
AG>1. Т.к. спецификация исключений не поддерживается, решили не требовать от разработчиков их писать в наследниках и не указывать их в стандартной библиотеке и её документации.
Спецификации поддерживаются.
AG>2. Так сложилось исторически:
Были другие breaking changes, пережили. Я скорее поверю, что /d1ESrt не документируют по историческим причинам. Даже боюсь представить что будет, если он станет широкоизвестен.
AG>Я поверю в наличие "поднятого" ообработчика только кода увижу ситуацию, когда он не выбрасывается.
Она в самом первом моем посте. Просто у тебя зуб на /d1ESrt
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Здравствуйте, gear nuke, Вы писали:
AG>>Как с — не важно.
GN>Почему считаешь не важным /d1ESrt? Потому что не используешь сам. Разработчики компилятора не могут руководствоваться такими соображениями. Им необходимо учесть все варианты.
Учесть все варианты — возможно. Оптимизировать имеет смысл лишь для практически используемых вариантов.
AG>>Теоретические предпосылки для оптимизации при наличии throw() просты: компилятор получает дополнительную информацию, при этом не обеспечивает никаких дополнительных гарантий. Соответственно он может или воспользоваться возмоностью для оптимизации, или нет, но испортить не может. То что throw() это __declspec(nothrow), а __delcpsec(nothrow) служит для оптимизации — это в MSDN сказано.
GN>Да, там сказано что это лучше в одном из случаев (/EHs). При этом там сказано вот что еще:
GN>
This attribute tells the compiler that the declared function and the functions it calls never throw an exception.
GN> то есть речь о функциях без определения. std::exception не тот случай, компилятор определение видит, чем ему помогает спецификатор?
Нет. Из выделенного не следует, что речь исключительно о функциях без определения.
AG>>У меня есть более правдоподобные версии.
AG>>1. Т.к. спецификация исключений не поддерживается, решили не требовать от разработчиков их писать в наследниках и не указывать их в стандартной библиотеке и её документации.
GN>Спецификации поддерживаются.
В "реальной жизни" описанная ситуация невозможна, спецификаторы исключений якобы не поддержаны,
AG>>2. Так сложилось исторически:
GN>Были другие breaking changes, пережили. Я скорее поверю, что /d1ESrt не документируют по историческим причинам. Даже боюсь представить что будет, если он станет широкоизвестен.
Но было и протягивание нестандартного поведения для совместимости. Хотя бы forScope или передачу временного объекта по неконстантной ссылке.
В данном случае throw() даст мало преимуществ, чтобы его стоило вводить.
AG>>Я поверю в наличие "поднятого" ообработчика только кода увижу ситуацию, когда он не выбрасывается.
GN>Она в самом первом моем посте.
Там он не выбрасывается но и не "поднимается", так что не проходит.
GN>Просто у тебя зуб на /d1ESrt
Здравствуйте, gear nuke, Вы писали:
GN>Спецификации поддерживаются. GN>Я скорее поверю, что /d1ESrt не документируют по историческим причинам. Даже боюсь представить что будет, если он станет широкоизвестен.
ESrt в х64 не поддерживаются, только в х86
Здравствуйте, byleas, Вы писали:
B>ESrt в х64 не поддерживаются, только в х86
Дык переусложнили EH и не справились наверное но это мелочи
Лучше скажи что-нибудь про std::exception это делалось на заре разбирательств с EH, когда я еще постоянно смотрел генерируемый код, поэтому я уверен что это оптимизация. Но почему-то особо дельше той ревизии не ушло...
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth