Моя вчерашняя заметка “Вот за что мне не нравится 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
Проспойлю... Dynamic_cast<> does some runtime type checking and the /GR- compiler flag
ОтветитьУдалить(which is the default) disables RTTI.
Вообще наплевать на непониматоров; проблемы надо обсуждать с теми, кто возможно их будет решать.
Хорошо бы обсудить "как должна *была бы* выглядеть в с++ поддержка умолчаний" (сюда входят как define, так и параметры по умолчанию в шаблонах, которые жцц разворачивает в сообщениях об ошибках). К "умолчаниям" еще надо добавить "соглашения", но это возможно более сложный вопрос.
> board.rt.mipt.ru.
ОтветитьУдалитьЗашел, почитал заголовки комментариев... Ой, Женя, ты туда больше не ходи :-), там кроме школьников (причем второгодников) никого нет. Лучше на ЛОР ходи. Там позавчера один анонимус с другим спорили про Ruby (новость, разумеется, про Python была). Причем очень аргументированно и содержательно спорили. Так забавно. Сначала один анонимус пишет, а затем другой ему отвечает (может их и больше было, кто их разберет...) и большей частью по делу.
И насчет той задачки для собеседования: в неравной и героической борьбе с ленью я даже скопильнул одно из решений на итераторах, затем стал обдумывать решение на строках... оно даже казалось мне короче... но вот что получается:
ОтветитьУдалитькак мы гарантируем неперерасход памяти, если:
1. лицензия состоит из очень длинных строк в перемешку с непустыми очень короткими
2. в файле встречаются как очень длинные, так и очень короткие строки
Если скользящее окошко будет всегда размером в к-во строк лицензии, то размер памяти под его данные может быть в M раз больше к-ва памяти лицензии, где М может быть даже порядка длина_лицензии_в_байтах/10
Этот комментарий был удален автором.
ОтветитьУдалитьдопустим, в лицензии 100 строк по 40 символов и 5 строк по 10000 символов;
ОтветитьУдалитьв файле все строки либо по 40 символов, либо по 10000 символов;
в случае если в файле подряд идут 105 строк по 10000 символов расход памяти будет?
если же предполагается какй-то более умный способ наполнения скользящего окошка, то его неплохо бы описать словами; у него, вероятно, снова будут недостатки.
пока blogstop тормозит, напишу еще чуть-чуть (и пойду гулять):
ОтветитьУдалитьMyClass в одной единице компиляции, скомпиленный с одиними опциями и дефайнами, это совсем не тот же тип (класс, ...), что он же, скопиленный в другой единице компиляции, с другими опциями или дефайнами; это однозначно проблема не инстумента, а языка, и искать этому надо языковое решение.
Рядом проблема -- это persistence объектов таких вот классов, *тоже* с разными опциями, а иногда и с одинаковыми!
Например, некий язык с выхлопом в с++ с похожим синтаксисом и семантикой, но дополнительными компайл-тайм проверками (и еще туда много че дополнительного можно добавить).
2имя:
ОтветитьУдалитьПо поводу C++. Я думаю, что C++ как раз велик тем, что его можно адаптировать к разным нишам. Где-то не нужны RTTI, где-то исключения. За счет этого он и держится.
Но, имхо, следовало бы сделать две вещи:
1. Уже давно пора было бы веделить некоторое подмножество C++ в более безопасный язык (хоть бы даже и под названием Enterprise С++). В котором нельзя было бы отключать RTTI, исключения и пр. стандартные возможности. В котором для примитивных типов данных были бы зафиксированы строгие размерности (скажем, int всегда 32-бита и все!). Может быть были бы запрещены некоторые преобразования указателей. Может быть, был бы урезан препроцессор.
2. Производителям компиляторов следовало бы сохранять в объектных файлах/библиотеках параметры компиляции. А линкеру следовало бы проверять опции разных объектников/библиотек на совместимость.
2имя:
ОтветитьУдалитьПо поводу тестовой задачи. Да, в сценарии со строками разной длины перерасход памяти наверняка будет. Но для тестовой задачи, на реализацию и тестирование которой отводится ограниченное время -- это вполне допустимо.
А соискатель, который бы еще эту проблему озвучил (пусть даже в комментариях в исходниках) сразу бы заработал себе очень большой и жирный плюс.
2san:
ОтветитьУдалитьЯ по статистике посещаемости увидел, что толпа народу оттуда ко мне ломится, вот и интересно стало, что и почему.
>Лучше на ЛОР ходи.
Новости на главной я там регулярно читаю. Ходить по форумам и активно участвовать уже не могу, не хватает ни времени, ни сил.
А вообще, по количеству интересных и грамотных людей LOL не сильно RSDN-у уступает (если вообще уступает).
ЕО>1. Уже давно пора было бы веделить некоторое подмножество C++ в более безопасный язык (хоть бы даже и под названием Enterprise С++). В котором нельзя было бы отключать RTTI, исключения и пр. стандартные возможности. В котором для примитивных типов данных были бы зафиксированы строгие размерности (скажем, int всегда 32-бита и все!). Может быть были бы запрещены некоторые преобразования указателей. Может быть, был бы урезан препроцессор.
ОтветитьУдалитьObjective C?
ЕО> А вообще, по количеству интересных и грамотных людей LOL не сильно RSDN-у уступает (если вообще уступает).
а куда там заходить?
какого-то специализированного форума по плюсам не нашел, а все перечитывать никакого времени не хватит.
>Objective C?
ОтветитьУдалитьНесколько раз пытался смотреть на него. Так и не понял, как этой смесью C и SmallTalk можно пользоваться.
Меня бы вполне устроил Eiffel. Но это экономически не оправдано в СНГ. Да и библиотек для него вообще нет.
>а куда там заходить?
http://www.linux.org.ru/forum/development/ -- там обычно в названии темы указывают язык программирования.
Уже давно пора было бы веделить некоторое подмножество C++ в более безопасный язык
ОтветитьУдалитьПритом прототип для этого дела есть http://www.digitalmars.com/d/2.0/safed.html можно очень многое оттуда позаимствовать.
>Притом прототип для этого дела есть http://www.digitalmars.com/d/2.0/safed.html можно очень многое оттуда позаимствовать.
ОтветитьУдалитьУгу. По большому счету, и сам D мог бы стать таким языком. Если бы у него автор был более прагматичным.
2night beast: вот, кстати, еще о ЛОР-е. На RSDN еще не было новости, а на ЛОР-е обсуждают выход новой спецификации языка Haskell -- http://www.linux.org.ru/news/doc/5083611
ОтветитьУдалитьПо поводу тестовой задачи. Да, в сценарии со строками разной длины перерасход памяти наверняка будет.
ОтветитьУдалитьА в итераторном решении -- не будет.
Впрочем, окошко еще видимо можно спасти: делаем класс MyString, который не храниm0; данные тех строк, что длиннее самой длинной в лицензии (только длинну), и держим в окошке строк общей длинной <= длины_лицензии.
Монстровато и нереюзабельный велосипед, но реализуемо.
Итераторное решение состоит из 2 классов, первый из которых можно найти кажется в буст::спирит (но я его накатал сам в 30 строк):
/// главное -- итератор можно копировать, и он помнит позицию, и продолжает итерироваться оттуда;
/// итератор не инвалидируется при fseek(file), и сам вызывает fseek (при конструировании и изредка при инкременте);
/// два итератора равны при равенстве позиций от начала файла, равенство файлов не проверяется
/// после чтения с диска итератор позволяет сделать allowed быстрых инкрементов до следующего чтения с диска;
/// итератор позволяет сделать allowed быстрых декрементов, но только после соответствующего числа инкрементов;
/// декремент введен как (опасная) и быстрая альтернатива копированию итератора и инкременту копии;
///
class FileIterator: public std::iterator_traits
/// Iterator должен быть либо итератором двунаправленного доступа, либо FileIterator
template class NormalizedCharIterator: public std::iterator_traits
выхлоп ненормализованного файла делаем так: у NormalizedCharIterator имеется метод origin(), который возвращет "настоящий" итератор; файл через итератор прогоняется до тех пор, пока окажется не равен origin-у найденной лицензии
Нереюзабельного велосипедостроительства -- ноль.
блин, вырезало "тэги"
ОтветитьУдалитьclass FileIterator: public std::iterator_traits< char* >
template< class Iterator > class NormalizedCharIterator: public std::iterator_traits< Iterator >
Угу. По большому счету, и сам D мог бы стать таким языком. Если бы у него автор был более прагматичным.
ОтветитьУдалитьзапрещать во *всем* языке опасные фичи нельзя; в то же время, безопасный подъязык должен проверяться компилятором (хотя бы через ключ ком. строки)
Производителям компиляторов следовало бы сохранять в объектных файлах/библиотеках параметры компиляции. А линкеру следовало бы проверять опции разных объектников/библиотек на совместимость.
Хрен их дождешься. А при наличии своего языка с выхлопом в с++ можно было бы опции и дефайны в выхлопном файле кодировать в пространстве имен:
using namespace _922048719823348337283;
или вообще взять значительную часть сборки в свои руки... но я об этом не думал.
ЛОР стараниями модераторов теряет былую привлекательность и скатывается в Основной Продукт Жизнедеятельности; но все еще человек 20-30 полезных собеседников там есть.
ОтветитьУдалитьNB>>>а куда там заходить?
ОтветитьУдалитьЕА>http://www.linux.org.ru/forum/development/
спасиб. подписался, посмотрю чего народ пишет.
2имя:
ОтветитьУдалить>Итераторное решение состоит из 2 классов, первый из которых можно найти кажется в буст::спирит (но я его накатал сам в 30 строк):
А это решение можно где-нибудь увидеть?
>А при наличии своего языка с выхлопом в с++ можно было бы опции и дефайны в выхлопном файле кодировать в пространстве имен:
using namespace _922048719823348337283;
Имхо, по-моему, компиляторы Eiffel так и делают.
Да Хаскелисты на rsdn совсем мышей не ловят. Хотя они, и функциональщики вообще, теперь больше по блогам тусуются http://fprog.ru/planet/
ОтветитьУдалитьВообще в функциоанльщине в этом году большие подвижки, зарелизился F# выйдет через месяц - другой OCaml 3.12 (язык очень существенно улучшается, поэтому совершенно непонятно как они нумеруют версии) и вот появился новый стандарт Хаскеля (интересно компилятор когда будет?)
2Rustam:
ОтветитьУдалить>Вообще в функциоанльщине в этом году большие подвижки
Ну, разбредание функциональщиков по блогам и подвижки могут говорить об одном из двух:
- либо функциональщина таки проникает в мейнстрим;
- либо этот пузырь все-таки сдувается.
Скажу честно, меня устраивает любой из этих вариантов :)))
- либо функциональщина таки проникает в мейнстрим;
ОтветитьУдалить- либо этот пузырь все-таки сдувается.
Одно другому не мешает, пузырек похоже уже прошел вторую фазу из http://en.wikipedia.org/wiki/Hype_cycle так что я жду в ближайшее время обличительных статей о том как функциональщики всех нае кхм обманули :)
А это решение можно где-нибудь увидеть?
ОтветитьУдалитьщас оно просто скомпильнутое, но не падало и даже производило некую деятельность по замене; вообще выставить недоделанное как-то не очень... хотя х.з.
смысл довести это до ума есть в том случае, если мы затеим дискуссию, ты (мне больше на ты нравится) поищешь в нем недостатки/сложности по сравнению с окном со строками и т.п. ... а тот вариант, что щас есть у меня, достаточен для того, чтобы мне сделать интересные для меня выводы
писать код окна со строками мне влом; можно было бы сравнить "очные" ит 077;раторы с "заочным" окном, но хотя бы текстовую описаловку и объявления классов надо иметь "очно"
(полный код, конечно, даст больше возможностей сравнить к-во мелких сложностей, идеальных для возниквения багов)
З.Ы. "щас" это мое личное мнение о русском правописании, а не ошибка
ну или подождать... вдруг у меня будет приступ энтузиазма... но я все же вижу очень мало смысла выставлять что-то, если оно НЕ будет подвергнуто критике
ОтветитьУдалитьчем кстати ЛОР (и был) хорош -- там в критике не стесняются
>смысл довести это до ума есть в том случае, если мы затеим дискуссию, ты (мне больше на ты нравится) поищешь в нем недостатки/сложности по сравнению с окном со строками и т.п. ... а тот вариант, что щас есть у меня, достаточен для того, чтобы мне сделать интересные для меня выводы
ОтветитьУдалитьНет проблем -- если захочешь, высылай свой вариант, а я выскажу свои замечания (если таковые будут).