none
Баг компилятора MS Visual Studio 2012?

    Вопрос

  • Доброго времени суток.

    Собственно вопрос обозначен в названии темы. Далее привожу примеры.

    1. Вот первый, простой пример:

    /*
    test1.cpp
    © Andrey Bushman, 18 Jun 2013
    */
    //--------------------------------------------
    #include <exception>
    #include <iostream>
    using namespace std;
    //--------------------------------------------
    void func2(){ // definition
    	void func1(); // declaration
    	func1(); // calling
    }
    //--------------------------------------------
    void func1(){ // definition
    	cout << "Ping..." << endl;
    }
    //============================================
    int main()
    try{	
    	func2();
    }
    catch(exception& e){
    	cerr << e.what() << endl;
    	return 1;
    }
    catch(...){
    	cerr << "Unknown exception." << endl;
    	return 2;
    }

    Этот код успешно компилируется командой cl test1.cpp /EHsc и работает как нужно. Идём далее...

    2. Вариант второй: вносим небольшие изменения, а именно - размещаем функции не в глобальном пространстве имён, а в отдельном:

    /*
    test2.cpp
    © Andrey Bushman, 18 Jun 2013
    */
    //--------------------------------------------
    #include <exception>
    #include <iostream>
    using namespace std;
    //--------------------------------------------
    namespace Bushman{
    //--------------------------------------------
    	void func2(){ // definition
    		void func1(); // declaration
    		func1(); // calling
    	}
    //--------------------------------------------
    	void func1(){ // definition
    		cout << "Ping..." << endl;
    	}
    }
    //============================================
    int main()
    try{
    	namespace B = Bushman;
    	B::func2();
    }
    catch(exception& e){
    	cerr << e.what() << endl;
    	return 1;
    }
    catch(...){
    	cerr << "Unknown exception." << endl;
    	return 2;
    }
    Пробуем собрать проект командой cl test2.cpp /EHsc, но получаем следующее:

    C:\bs\13>cl test2.cpp /EHsc
    Microsoft (R) C/C++ Optimizing Compiler Version 17.00.51106.1 for x64
    Copyright (C) Microsoft Corporation.  All rights reserved.

    test2.cpp
    Microsoft (R) Incremental Linker Version 11.00.51106.1
    Copyright (C) Microsoft Corporation.  All rights reserved.

    /out:test2.exe
    test2.obj
    test2.obj : error LNK2019: unresolved external symbol "void __cdecl func1(void)"
     (?func1@@YAXXZ) referenced in function "void __cdecl Bushman::func2(void)" (?fu
    nc2@Bushman@@YAXXZ)
    test2.exe : fatal error LNK1120: 1 unresolved externals

    C:\bs\13>

    Как видим, возникла проблема с линковкой для функции func1. Идём далее...

    3. Выносим объявление функции func1 за рамки определения функции func2, размещая его непосредственно в пользовательском пространстве имён:

    /*
    test2.cpp
    © Andrey Bushman, 18 Jun 2013
    */
    //--------------------------------------------
    #include <exception>
    #include <iostream>
    using namespace std;
    //--------------------------------------------
    namespace Bushman{
    	void func1(); // declaration
    //--------------------------------------------
    	void func2(){ // definition		
    		func1(); // calling
    	}
    //--------------------------------------------
    	void func1(){ // definition
    		cout << "Ping..." << endl;
    	}
    }
    //============================================
    int main()
    try{
    	namespace B = Bushman;
    	B::func2();
    }
    catch(exception& e){
    	cerr << e.what() << endl;
    	return 1;
    }
    catch(...){
    	cerr << "Unknown exception." << endl;
    	return 2;
    }
    Этот вариант успешно компилируется и работает: cl test3.cpp /EHsc.

    Наблюдаемое поведение является некорректным.

    Все приведённые выше примеры успешно компилируются другими компллияторами, например Cygwin g++ 3.4.4, или GCC g++ в Linux.

    Вывод: линковщик MS Visual Studio 2012 не может найти нужной функции видимо потому, что компилятор неверно обрабатывает объявления функций, размещённых в определениях других функций, если те находятся не в глобальном пространстве имён. В этом случае компилятор формирует неверную информацию для линковщика и тот ищет определение в глобальном пространстве имён, а не в том, в котором нужно.

    Если ошибаюсь, прошу меня поправить...

    Спасибо.






    19 июня 2013 г. 9:20

Ответы

  • Не совсем понял, причем тут выделение памяти. Предварительная декларация функции добавляет имя в пространство имен. И по этому имени функцию можно вызвать. Выделение памяти к этому вообще никакого отношения не имеет.

    Возможно, этот note не имеет отношения к проблеме. Может быть не компилируется из-за того, что у имени объявленного в блоке нет linkage (если не указан extern, 3.5, 8, и если имя не было объявлено раньше (в этом случае linkage унаследуется, 3.5, 6). а у функции объявленной в неймспейсе - external linkage (по 3.5, 4). И имена считаются разными по 3.5, 9.

    В любом случае - считаете поведение багом - пишите в коннект. Я бы на вашем месте уже написал.

    Насчет существующего бага в connect - там в примере явно задан extern linkage, и с ним сейчас все нормально компилируется:

    namespace SomeNamespace {
    	void func2(){ 
    		extern void func1(); 
    		func1(); 
    	}
    
    	void func1(){
    	}
    }

    Ищите решение (фикс, если считаете поведение багом) там, где баг могут исправить. 

    19 июня 2013 г. 18:38
    Модератор

Все ответы

  • Вроде-бы все работает как сказано в стандарте (http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3242.pdf)

    § 3.3.2, 10:

    Function declarations at block scope ... refer to declarations that are members of
    an enclosing namespace, but they do not introduce new names into that scope.

    Т.е. нельзя просто так взять и добавить имя func1 в неймспейс Bushman из блока func 2{}.

    Давно не писал на плюсах, и работает как-то неоднозначно. Запостите баг в connect, на всякий случай, только упростите пример (выбросьте try/catch, чтобы не смущало)

    namespace SomeNamespace {
    	void func2(){ // definition
    		void func1(); // declaration, should refer void SomeNamespace::func1();
    		func1(); // calling
    	}
    
    	void func1(){ // definition
    	}
    }


    19 июня 2013 г. 13:02
    Модератор
  • @PashaPash,

    Насколько я вижу, в указанном вами § 3.3.2, 10 пишется как раз о том, что я прав: если в объявлении не указан нэймспейс,
    то поиск определения должен осуществляться в том нэймспейсе, в котором это объявление выполнено. И это вовсе не
    означает то, что вы написали:

    > Вроде-бы все работает как сказано в стандарте...
    > Т.е. нельзя просто так взять и добавить имя func1 в неймспейс Bushman из блока func 2{}.

    Как раз таки можно.

    § 3.3.2, 10:
     
    Оригинал:
    [ Note: Friend declarations refer to functions or classes that are members of the nearest enclosing namespace,
    but they do not introduce new names into that namespace (7.3.1.2). Function declarations at block scope
    and variable declarations with the extern specifier at block scope refer to declarations that are members of
    an enclosing namespace, but they do not introduce new names into that scope. — end note ]

    Перевод:
    [ Примечание: Дружественные объявления относятся к функциям или классам, которые являются членами ближайшего 
    включающего пространства имён, но они не вводят новые имена в это пространство имён (7.3.1.2). Функции, 
    объявленные в области видимости блока, а так же объявления переменных, указанных с внешним спецификатором 
    области видимости относятся  к объявлениям, которые являются членами включающего пространства имён, но они не 
    вводят новых имён в это пространство. - конец примечания]

    > Давно не писал на плюсах, и работает как-то неоднозначно.
    > declaration, should refer void SomeNamespace::func1();

    Лукавите. Это 100% не работает и объявление с указанием пространств имён не устраняет проблему. Я, разумеется, 
    проверял это, прежде чем создать данный топик.

    Кроме того, насколько я помню, объявление функций в коде некоторых методов я перенял у Беарне Стровструпа, из 
    книги "Программирование, принципы и практика использования C++". В некоторых примерах он для наглядности так делал.

    > Запостите баг в connect, на всякий случай.

    Я не знаю где этот connect, поэтому сообщил о баге на форуме Майкрософта.

    С уважением, Андрей
    19 июня 2013 г. 15:06
  • >> Лукавите. Это 100% не работает и объявление с указанием пространств имён не устраняет проблему. Я, разумеется, 
    проверял это, прежде чем создать данный топик.

    Я написал во что должно превратиться void func1() по спеке, а не "вот такой код должен сработать". На случай, если вы решите постить код в коннект. Лучше сразу написать, чего вы ожидаете от компилятора, хотя бы в комментах - т.к. опция /Ehsc и код с try/catch к проблеме никакого отношения не имеет.

    Не совсем понял, почему "как раз таки можно" - даже в русском переводе видно, что объявление func1 в скоупе блока не введет (не добавит) видимое имя SomeNamespace::func1(). Раз имя в неймспейсе не вводится - func1 считается не принадежащей SomeNamespace.

    Если func1 уже было объявлено, даже без реализации - то линкуется корректно:

    namespace SomeNamespace {
    	void func1(); 
    
    	void func2(){ // definition
    		void func1(); // declaration, should refer void SomeNamespace::func1();
    		func1(); // calling
    	}
    
    	void func1(){ // definition
    	}
    }

    Вот этот коннект: https://connect.microsoft.com/VisualStudio

    Форумы MS - это скорее место для общения с другими разработчиками, а не способ отрепортить баг. Шанс, что сюда зайдет кто-то из команды компилятора C++, знающий русский язык, и способный исправить баг - почти никакой (хоть и не нулевой)

    19 июня 2013 г. 15:44
    Модератор
  • кстати, вот оно: https://connect.microsoft.com/VisualStudio/feedback/details/730878/inconsistent-c-namespace-resolution-in-forward-declarations-made-at-function-scope

    Closed as deferred (aka починим когда нибудь). Можете проголосовать за проблему, иногда повышает шанс фикса.

    19 июня 2013 г. 15:51
    Модератор
  • @PashaPash,

    >Не совсем понял, почему "как раз таки можно" - даже в русском переводе видно, что объявление func1 в скоупе блока не введет (не добавит) видимое имя SomeNamespace::func1(). Раз имя в неймспейсе не вводится - func1 считается не принадежащей SomeNamespace.

    Полагаю, что мы с вами по разному понимаем фразу "введение нового имени". Я понимаю это так, что объявление функции, заданное в виде

    void SomeNamespace::func1();

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

    Кроме того, согласно тому же переводу, я понимаю так, что если в объявлении функции не указано пространство имён, то по умолчанию должно подставляться то пространство имён, в котором это объявление и находится.

    >кстати, вот оно

    Да, любопытно, спасибо. Только я не понял одного: Майкрософт пишет:

    >We have confirmed that this is a bug in our compiler and it has been fixed.

    т.е. по-русски это примерно так:

    > Мы подтвердили, что это является ошибкой нашего компилятора и это было исправлено (или зафиксировано).

    Тут не ясно, в каком контексте правильно переводить слово "fixed" - исправлено, или же зафиксировано... Судя по тому, что в MS Visual Studio 2012 баг присутствует, скорее всего вариант второй. :(


    19 июня 2013 г. 16:51
  • Не совсем понял, причем тут выделение памяти. Предварительная декларация функции добавляет имя в пространство имен. И по этому имени функцию можно вызвать. Выделение памяти к этому вообще никакого отношения не имеет.

    Возможно, этот note не имеет отношения к проблеме. Может быть не компилируется из-за того, что у имени объявленного в блоке нет linkage (если не указан extern, 3.5, 8, и если имя не было объявлено раньше (в этом случае linkage унаследуется, 3.5, 6). а у функции объявленной в неймспейсе - external linkage (по 3.5, 4). И имена считаются разными по 3.5, 9.

    В любом случае - считаете поведение багом - пишите в коннект. Я бы на вашем месте уже написал.

    Насчет существующего бага в connect - там в примере явно задан extern linkage, и с ним сейчас все нормально компилируется:

    namespace SomeNamespace {
    	void func2(){ 
    		extern void func1(); 
    		func1(); 
    	}
    
    	void func1(){
    	}
    }

    Ищите решение (фикс, если считаете поведение багом) там, где баг могут исправить. 

    19 июня 2013 г. 18:38
    Модератор
  • там в примере явно задан extern linkage

    Но ведь по правилам extern используется в том случае, когда определение находится в другом исходном файле, а не в этом же, насколько я помню из K&R. Или я ошибаюсь? 

    Спасибо.

    20 июня 2013 г. 6:06