On-line: гостей 0. Всего: 0 [подробнее..]
Программисты всех стран, объединяйтесь!

АвторСообщение



ссылка на сообщение  Отправлено: 28.07.12 20:16. Заголовок: MS VC++ 2010: когда баг не является багом, а является расширением языка


При компиляции программ компилятором MS VC++ 2010 нужно быть очень осторожным и внимательным.

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

Во-вторых, надо знать, что используемая вами некорректная конструкция, если таковая присутствует в вашем коде, не является на самом деле багом компилятора MS VC++ 2010, а является расширением языка С++ компилятора, которая по вашему желанию может быть отключено.

Вот пример кода, демонстрирующий сказанное.

#include "stdafx.h" 

struct A
{
A() {}
A( A & ) {}
};

void f( A ) {}

int _tmain(int argc, _TCHAR* argv[])
{
{
f( A() );
}

return 0;
}


Если запустить этот код на компиляцию с помощью компилятора MS VC++ 2010, то этот код успешно скомпилируется. Но является ли он корректным с точки зрения стандарта С++?

Увы, этот код совершенно некорректный. В классе A имеется конструктор копирования, который в качестве параметра принимает неконстатную ссылку на объект класса:
A( A & ) {}

В теле же функции main вызывается функция f, которой в качестве аргумента передается временный неименованный объект A(). А временные неименованные объекты могут связываться лишь с константной ссылкой на объект. То есть если бы конструктор копирования был объявлен следующим образом:

A( const A & ) {}

то код был бы корректным. Но этого нет. Поэтому код не должен компилироваться. Тем не менее он компилируется.

Это баг компилятора MS VC++ 2010? Нет! Это не баг компилятора, а его расширение языка С++! Вы можете отключить расширения языка С++ компилятора MS VC++ 2010, если хотите, чтобы компиляторам следовал нормам стандарта С++.

Для этого выберите в глановм меню пункт "Проект", а в нем "Свойства вашего_проекта".
На появившейся странице свойств вашего проекта выберите пункт "С/С++", а в нем подпункт "Язык". В правой части страницы свойств вашего проекта выберите пункт "Отключить расширенипя языка", в котором выберите опцию "Да".

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


 цитата:
error C2664: f: невозможно преобразовать параметр 1 из "A" в "A"
1> Не удается копировать конструкцию struct "A", так как конструкторы копий неоднозначны или отсутствуют



Надо иметь в виду, что по умолчанию для компилятора MS VC++ 2010 расширения языка С++ включены. Об этом не стоит забывать, чтобы адекватно реагировать на результаты компиляции вашего кода компилятором.

Вот что по этому поводу говорят сами разработчики компилятора MS VC++ 2010 :


 цитата:
Posted by Microsoft on 31.05.2012 at 14:33

Thank you for contacting Microsoft. For legacy compatibility, we allow binding of A& to rvalues in a two-phase manner. First we try the strict Standard semantics, allowing only const A& to bind to rvalues. If that fails, we fall back and allow A& to bind. This is what you are seeing. To enforce Standard semantics in this case, you can compile with /Za.

Perhaps in the future we shall make /Za behaviour the default but our experience shows that that results in heavy customer complaints when their code breaks.

Tanveer Gani
Visual C++ Team.



Спасибо: 0 
ПрофильЦитата Ответить
Ответов - 5 [только новые]





ссылка на сообщение  Отправлено: 28.07.12 20:17. Заголовок: Также не следует пре..


Также не следует пребывать в плену иллюзии, что вы имеете дело с расширением языка компилятора MS VC++, когда его используете в качестве базового компилятора. Будет полезно проверять работу кода, отключив в свойствах проекта режим расширения языка С++. Но даже если вы отключите режим расширения языка С++ компилятора MS VC++, и код у вас успешно будет скомпилирован, это не означает, что этот код корректный. Попробуйте этот же код скомпилировать с помощью другого компилятора.

В качестве примера приведу следующий код.

#include "stdafx.h" 

struct A
{
int x;
};

A f()
{
A a = { 10 };

return a;
}


int _tmain(int argc, _TCHAR* argv[])
{
f().x = 20;

return 0;
}


Этот код успешно компилируется и выполняется компилятором MS VC++ 2010. Но вызывает сомнение предложение f().x = 20;. Здесь изменяется значения объекта, который не является lvalue. Может быть это расширение языка С++ компилятора MS VC, как это имело место в предыдущем примере? Но если для проекта в его свойствах отключить режим использования расширения языка, то код все равно успешно скомпилируется и выполнится.

Означает ли это, что код действительно корректный? Нет, конечно. Этот код некорректный. Нельзя изменять значение временного неимнованного объекта. И убедиться в этом довольно легко, если попробовать откомпилировать этот код с помощью компилятора GCC 4.7.0. Компилятор сразу же сообщит об ошибке компиляции.

Итак, это не расширение языка С++ компилятора MS VC++, а его баг. Что подтверждает команда разработчиков компилятора Майкрософт MS VC++ 2010:


 цитата:
Posted by Microsoft on 18.06.2012 at 13:00

Thank you for reporting this issue. We do have plans to improve our conformance to the Standard but we regret this issue comes too late to fix in the VC++11 release.

Tanveer Gani
Visual C++ Team



Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 24.08.12 22:49. Заголовок: Вот еще один пример ..


Вот еще один пример того, когда работа программы может привести к неожидданнным для вас результатам в зависимости от того, включена ли опция компилятора MS VC++ 2010 по использованию расширений языка или нет.

Рассмотрим следующий код.

 #include "stdafx.h" 
#include <iostream>


int fn() { return 0; }

void f( bool ) { std::cout << "f( bool )\n"; }
void f( void * ) { std::cout << "f( void * )\n"; }

int _tmain(int argc, _TCHAR* argv[])
{
int *p = 0;
f( p );
f( fn );
std::cout << fn << std::endl;

return 0;
}


Возникает вопрос, какая из двух перегруженных функций с именем f будет вызвана, когда в качестве аргумента будет использовано имя функции fn?

Допустим, вы этого не знаете, а потому просто решили проверить, какую функцию будет вызывать сгенерированный компилятором объектный код программы, запустив исходный пример на компиляцию и выполнение.
Если при этом в свойствах проекта для опции "Отключить расширения языка" у вас установлено значение "Нет", то вы получите следующий вывод на консоль:


 цитата:
f( void * )
f( void * )
00D31113



Как из него видно, для обоих указателей, для указателя на int и указателя на функцию fn (а имя fn будет преобразовано в указатель на fn), вызывается функция void f( void * ).

В предложении

	std::cout << fn << std::endl;


также происходит преобразваоние имени fn в указатель на эту функцию, который в свою очередь преобразуется в указатель void *, а для типа void * в классе потоков существует перегруженный опреатор operator << (const void * ), который и вызывается в указанном предложении. То есть на консоль выводится адрес функции.

Итак, проверив работу кода, вы пришли к твердому убеждению, что из двух перегруженных функций, одна из которых принимает указатель на vkid, а другая - значение типа bool, для аргумента в виде имени функции будет вызываться первая функция. То есть указатель на функцию будет неявно преобразовываться в указатель на void.

Вы работаете со своим проектом, и работа кода вашей программы доя вас понятна и предсказуема. Но в один прекрасный день по каким-то соображениям вы решили для опции "Отключить расширения языка" установить значение "Да". Возможно, вы решили это сделать именно из тех соображений, чтобы избежать использования не соответствующей стандарту конструкции, когда временный неименованный объект связывается с неконстантной ссылкой, как это было описано в первом сообщении этой темы.
Вы естественно предполагали, что ваша программа по-прежнему будет успешно работать, так как в своем коде вы нигде не связываете временный неименнованный объект с неконстатной ссылкой. Но к вашему удивлению программа стала вести себя иначе и выдавать не тот результат, на который вы рассчитывали.

Так что же изменилось?

А изменения затронули и тот код, где выбиралась перегруженная функция, когда в качестве аргумента передавался указатель на функцию.
Давайте посмотрим, как будет работать исходный пример, если для опции "Использовать расширения языка" установить значение "Нет".


 цитата:
f( void * )
f( bool )
1



Этот результат может повергнуть в шок, не готового к такому изменению работы исходного примера программиста.

Сравним его с первым результатом. Когда в качестве аргумента для функции f используется указатель на int, то в обоих случаях вызывается одна и та же функция void f( void * ).

На этом сходство заканчивается.

Для аргумента в виде fn теперь вместо ожидаемого вызова функции, приниаающий указатель на void, вызывается уже функцию void f( bool ).
В третьей строке вывода на консоль уже выводится не адрес функции fn, как ранее это делалось, а значение, равное 1. Не трудно догадаться, что это выводится булево значение true, а в соответствующей строке вместо оператора operator <<( const void *) задействуется оператор operator <<( bool ).

Итак, при установке опции свойств проекта "Использовать расширения языка" в значение "Нет" для аргумента в виде fn из двух перегруженных функций f вызывается та функция, которая имеет тип параметра bool.

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

Обратимся к стандарту С++. Почему для указателя на int вызывается перегруженнная функция void f( void * ), в то время как для указателя на функцию вызывается перегруженная функция void f( bool )?

В параграфе №2 раздела 4.10 Pointer conversions описываются преобразования для указателей.


 цитата:
2 A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer to cv void”. The result of converting a “pointer to cv T” to a “pointer to cv void” points to the start of the storage location where the object of type T resides, as if the object is a most derived object (1.8) of type T (that is, not a base class subobject). The null pointer value is converted to the null pointer value of the destination type.



Обратите внимание на выделенную часть текста жирным шрифтом. Такое преобразование в указатель на void возможно для указателей на объекты, а не на функции.
Неявное преобразование для указателей на функции описано в параграфе №1 раздела
4.12 Boolean conversions


 цитата:
1 A prvalue of arithmetic, unscoped enumeration, pointer, or pointer to member type can be converted to a prvalue of type bool. A zero value, null pointer value, or null member pointer value is converted to false; any other value is converted to true. A prvalue of type std::nullptr_t can be converted to a prvalue of type bool; the resulting value is false.



В этом параграфе речь идет о любых указателях, включая и указатели на функции.
Так как указатель на функцию не может быть неявно преобразован в указатель на void, но может быть неявно преобразован в к типу bool, то для имени функции fn, задаваемого в качестве аргумента, из двух перегруженных функций f вызывается та из них, которая имеет объявление void f( bool ).

Это еще раз демонстрирует, насколько важно знать стандарт языка С++, а не доверяться результатам работы того или иного компилятора.

Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 17.10.12 17:59. Заголовок: Еще один пример, кот..


Еще один пример, который можно поместить в копилку расширений языка C++ компилятора MS VC++ 2010. Если в свойствах проекта опция "Отключить расширения языка" имеет значение "Нет", то следующий код будет успешно компилироваться.

 #include "stdafx.h" 


int _tmain(int argc, _TCHAR* argv[])
{
struct A { enum E; };
enum A::E { e1, e2 };

return 0;
}


В этом коде элементы перечисления определяются вне локального класса A.

Если же вы в свойствах проекта зададите для опции "Отключить расширения языка" значение "Да", то приведенный пример кода уже не будет компилироваться. Компилятор выдаст следующее сообщение об ошибке


 цитата:
error C2599: E: опережающее объявление перечисляемого типа не разрешено



Теперь возникает вопрос, является ли код корректным с точки зрения стандарта C++?

Нет, такое определение значение перечисления вне класса является некорректным, так как значения (unscoped) перечисления являются членами класса, а потому должны быть объявлены в определении класса.

Многие программисты забывают, что элементы перечисления являются членами класса, а потому для них оказывается неожиданным такая синтаксическая конструкция.

 #include "stdafx.h" 
#include <iostream>

int _tmain(int argc, _TCHAR* argv[])
{
struct A { enum E { e1, e2 }; };

A a;
std::cout << a.e1 << std::endl;

return 0;
}


То есть когда вместо A::e1 для доступа к элементу перечисления e1 используется объект класса, как в примере, a.e1

Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 18.10.12 00:36. Заголовок: Вообще-то, предыдущи..


Вообще-то, предыдущий вопрос о перечислениях не простой.
Я столкнулся с тем, что компилятор GCC 4.7.1 компилирует следующий код

 int main() 
{
struct A { enum E : int; };
enum A::E : int { e1, e2 };
}


На мой взгляд этот код не должен компилироваться и противоречит стандарту C++. На чем основано мое убеждение? Согласно параграфу №1 раздела 9.2 Class members


 цитата:
1 The member-specification in a class definition declares the full set of members of the class; no member can be added elsewhere.



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

В этом же параграфе далее написано следующее:


 цитата:
The enumerators of an unscoped enumeration (7.2) defined in the class
are members of the class.



То есть перечислительные константы unscoped перечисления являются членами класса.

Но в таком случае они должны быть определены внутри определения класса! Так как из ранее процитированной первой фразы этого параграфа следует, что никакие члены класса не могут быть добавлены к классу после его определения. Из примера же кода следует, что эти перечислительные константы были добавлены уже после определения класса.

Вопрос требует разъяснения, и я открыл соответствующую тему на форуме по обсуждению стандарта C++. Будет интересно узнать, что там скажут по этому вопросу.

Спасибо: 0 
ПрофильЦитата Ответить



ссылка на сообщение  Отправлено: 18.10.12 01:31. Заголовок: Как сообщил Daniel K..


Как сообщил Daniel Krügler, этот вопрос сейчас обсуждается в комитете по стандартизации. Он предоставил ссылку на перечень активных вопросов, которую я здесь и дублирую:
C++ Standard Core Language Active Issues, Revision 80

Мой вопрос, фактически, идентичен вопросу "1485. Out-of-class definition of member unscoped opaque enumeration" из указанного перечня.

Спасибо: 0 
ПрофильЦитата Ответить
Ответ:
1 2 3 4 5 6 7 8 9
большой шрифт малый шрифт надстрочный подстрочный заголовок большой заголовок видео с youtube.com картинка из интернета картинка с компьютера ссылка файл с компьютера русская клавиатура транслитератор  цитата  кавычки моноширинный шрифт моноширинный шрифт горизонтальная линия отступ точка LI бегущая строка оффтопик свернутый текст

показывать это сообщение только модераторам
не делать ссылки активными
Имя, пароль:      зарегистрироваться    
Тему читают:
- участник сейчас на форуме
- участник вне форума
Все даты в формате GMT  3 час. Хитов сегодня: 3
Права: смайлы да, картинки да, шрифты да, голосования нет
аватары да, автозамена ссылок вкл, премодерация откл, правка нет