вторник, 6 июля 2010 г.

[prog.flame] В продолжение вчерашней темы – мой ответ экспертам board.rt.mipt.ru

Моя вчерашняя заметка “Вот за что мне не нравится C++” засветилась на форуме board.rt.mipt.ru. Как и следовало ожидать, тамошними экспертами я был объявлен тупым и криворуким.

Понятное дело, слышать, что у тебя “такой маленький” неприятно даже в том случае, если это далеко не так. Так что запасаемся линейками и приступаем :)

Для начала я объясню со слайдами, в чем была проблема у Стива Хьюстона с классом ACE_INET_Addr, поскольку один из высказавшихся экспертов, очевидно, не понял о чем речь. В классе ACE_INET_Addr имеется такой код (из ACE 5.6.8):

/**
* @class ACE_INET_Addr
*
* @brief Defines a C++ wrapper facade for the Internet domain address
* family format.
 */
class ACE_Export ACE_INET_Addr : public ACE_Addr
{
  ...
private:
  /// Underlying representation.
  /// This union uses the knowledge that the two structures share the
  /// first member, sa_family (as all sockaddr structures do).
  union
  {
    sockaddr_in  in4_;
#if defined (ACE_HAS_IPV6)
    sockaddr_in6 in6_;
#endif /* ACE_HAS_IPV6 */
  } inet_addr_;
};

Обратить внимание следует на поле inet_addr_, которое является объединением. Если при компиляции определен символ ACE_HAS_IPV6, то объединение включает и sockaddr_in, и sockaddr_in6, а размер inet_addr_ определяется размером sockaddr_in6 (т.к. эта структура больше). Если же ACE_HAS_IPV6 при компиляции не определен, то inet_addr_ содержит только sockaddr_in.

Так вот проблема Стива Хьюстона была в том, что сама библиотека ACE была скомпилирована с ACE_HAS_IPV6, т.к. ACE предполагала, что sizeof(inet_addr_) == sizeof(sockaddr_in6). Однако, прикладная программа была скомпилирована без ACE_HAS_IPV6. Соответственно, в программе sizeof(inet_addr_) == sizeof(sockaddr_in). Т.е. в программе под объект ACE_INET_Addr на стеке отводилось меньше места, чем на это расчитывала библиотека ACE. Поэтому происходило следующее: в программе на стеке отводилось место (23 байта под inet_addr_), далее запускался конструктор ACE_INET_Addr (запускался код из ACE), конструктор ACE_INET_Addr обнулял содержимое inet_addr_ – но не 23 байта, а 28 байт! Отсюда и грабли, на поиск которых было потрачено 5 часов рабочего времени.

А теперь парочка примеров того, как можно отгрести на ровном месте проблемы с C++ кодом, если чуть ошибиться с опциями компиляции для разных файлов. Ответов я давать не буду, пусть желающие попробуют предсказать результат с ходу. Примеры проверялись на Visual C++ 2003 и Visual C++ 2008. Архив с примерами находится здесь. Для компиляции достаточно зайти в нужный подкаталог и запустить в нем nmake.

Исходные тексты примеров так же можно увидеть под катом. Но сначала я хочу сказать, при чем здесь язык C++, ведь упомянутые ошибки не относятся напрямую к языку C++. Да, это не проблемы языка C++, это проблемы инструмента под названием C++. А вот как раз этот инструмент оказывается очень хрупким и чувствительным к настройкам компилятора и линкера. Вполне может быть, что аналогичными проблемами страдают и другие инструменты (тот же C, может быть Ada и, вполне возможно, Eiffel, т.к. он компилируется через C-шное представление). Но меня волнуют именно проблемы C++.

Первый пример:

  • файл t1_1.hpp
    #if !defined( T1_1_HPP )
    #define T1_1_HPP

    class base_t
       {
       public :
          virtual ~base_t() {}

          virtual const char *
          name() const = 0;
       };

    class d1_t : public base_t
       {
       public :
          virtual const char *
          name() const { return "d1"; }
       };

    class d2_t : public base_t
       {
       public :
          virtual const char *
          name() const { return "d2"; }
       };

    #endif
  • файл t1_1.cpp
    #include "t1_1.hpp"

    const char *
    f( const base_t & b )
       {
          const d1_t * d1 = dynamic_cast< const d1_t * >(&b);
          if( d1 )
             return "d1 detected";

          const d2_t * d2 = dynamic_cast< const d2_t * >(&b);
          if( d2 )
             return "d2 detected";

          return "nothing detected";
       }
  • файл t2_main.cpp
    #include <cstdio>

    #include "t1_1.hpp"

    const char * f( const base_t & b );

    int
    main()
       {
          d1_t d;

          const char * result = f( d );

          std::printf( "result: %s\n", result );
       }
  • файл Makefile
    t1.exe: t1_1.obj t1_main.obj
       link /SUBSYSTEM:CONSOLE /OUT:t1.exe  t1_1.obj t1_main.obj

    t1_1.obj:
       cl -c -GR t1_1.cpp

    t1_main.obj:
       cl -c -GR- t1_main.cpp

Второй пример:

  • файл t2_1.hpp
    #if !defined( T2_1_HPP )
    #define T2_1_HPP

    #include <cstdio>

    class some_raii_t
       {
       public :
          some_raii_t() { std::printf( "resource acquisition\n" ); }
          ~some_raii_t() { std::printf( "resource freeing\n" ); }
       };

    #endif
  • файл t2_a.cpp
    #include "t2_1.hpp"

    void b();

    void a()
       {
          some_raii_t raii;

          b();
       }
  • файл t2_b.cpp
    #include <stdexcept>

    void b()
       {
          throw std::runtime_error( "some exception" );
       }
  • файл t2_main.cpp
    #include <cstdio>
    #include <exception>

    void a();

    int
    main()
       {
          try
             {
                a();
                std::printf( "no exceptions\n" );
             }
          catch( const std::exception & x )
             {
                std::printf( "exception caught: %s\n", x.what() );
             }
       }
  • файл Makefile
    t1.exe: t2_a.obj t2_b.obj t2_main.obj
       link /SUBSYSTEM:CONSOLE /OUT:t2.exe t2_a.obj t2_b.obj t2_main.obj

    t2_a.obj:
       cl -c -GR t2_a.cpp

    t2_b.obj:
       cl -c -GR -EHs t2_b.cpp

    t2_main.obj:
       cl -c -EHs t2_main.cpp
Отправить комментарий