понедельник, августа 29, 2005

Разница между unspecified behavior и undefined behavior

В Стандарте языка C++ в основном требуется вполне определенное поведение системы, реализующей язык. Например, если x и y типа int и их сумма попадает в диапазон -INT_MAX...+INT_MAX, результат операции сложения x+y должен быть равен их арифметической сумме.
Но есть случаи, в которых делаются некоторые допущения по поведению. Случаи unspecified behavior и undefined behavior перепутать легче всего.

Unspecified behavior. Неспецифицированное поведение. Система может реализовать любое из поведений (по большей части для случаев unspecified behavior, Стандарт предлагает несколько поведений на выбор), не должна говорить какое именно реализовано и даже не обязательно, чтобы она вела себя в таких ситуациях одинаково внутри одной программы или функции. При этом предполагается, что на вход была подана правильная (well-formed) программа, соответсвующая синтаксическим, семантическим и прочим правилам языка.
Например: все аргументы функции должны быть вычислены до вызова функции, но они могут быть вычислены в любом порядке. Система не должна выбирать какой-то определенный порядок и не должна указывать по каким правилам она этот порядок выбирает, чтобы его можно было предугадать.

Undefined behavior. Неопределенное поведение. Это поведение может возникать только как реакция на неправильную программу. Далеко не все неправильные конструкции могут вызвать такое поведение, большинство ошибок требуют вполне определенную диагностику.
Undefined behavior означает, что стандарт не накладывает каких-либо ограничений. Может случиться все, что угодно.
Например: разыменование указателя, который равен NULL, может дать 0, или любое произвольное значение, или остановку программы, или сигнал какого-либо вида, или исключение, или реализация может послать email вашему боссу с рассказом о том, какой вы неаккуратный программист. Или все вместе. Или что-то еще.

По большому счету получается, что unspecified behavior означает "как будет работать толком неизвестно, но все будет хорошо". Если оно случается, то это, как правило, нормально и не стоит по его поводу беспокоиться. А undefined behavior означает "как будет работать толком неизвестно, но все будет плохо". И надо от него срочно избавляться, если для его присутствия в программе нет неких загадочных причин.

Это не все поведения, есть еще, но в отличие от двух предыдущих по их названиям можно ясно себе представить, что они означают:

Implementation-defined behavior. Поведение, зависящее от реализации. Похоже на unspecified behavior, но в документации к системе должно быть указано, какое именно поведение реализовано. И на это поведение можно полагаться.
Например: результат x%y, где x и y целые, а y отрицательное, может быть как положительным, там и отрицательным, но в документации к системе должно быть написано каким именно он будет.

Locale-specific behavior. Поведение, зависящее от локали. Причем речь тут идет не только о языке. Стандарт говорит о "национальных обычаях, культуре и языке". Поведение должно быть документировано.
Например: должна ли функция islower возвратить true для символов, не входящих в 26 маленьких латинских букв.

Ссылки по теме:
1996 С++ Drawft Standarts - Definitions
Unspecified versus undefined behavior

Technorati tag:

пятница, августа 26, 2005

Схема конвейера DirectX 8.1

Большая подробная схема The DirectX 8.1 Direct3D Graphics Pipeline. С ней хорошо читается ExtremeTech 3D Pipeline Tutorial.

вторник, августа 23, 2005

Зачем я веду этот блог

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

Качественной информации по программированию на русском языке очень мало. У меня же скопилось довольно много интересной информации, которой хочется поделиться, по многим темам я вообще ничего не могла найти в Рунете, хотя искала тщательно... Русскоязычные программисты очень любят писать по-английски. Или вот читаешь статью в солидном журнале, а там автор не делает вообще никакого различия между стандартом языка и его реализацией: "это скомпилялось в моем любимом компиляторе, значит в языке C++ эта конструкция должна работать именно так". Эх, был такой хороший журнал "Программист"... Нет, я не претендую на то, что мои посты являются каким-то откровением, но я тщательно разбираю вопрос, прежде чем написать что-либо в блог, и привожу ссылки на источники информации. Еще бывает, что статья хорошая, правильная, но рассказывается все так, что хочется с тоски удавиться. Мои посты тоже не всегда блещут весельем, но я стараюсь по мере сил делать их более интересными, менее занудными, а главное, как можно более понятными. И разбавлять блог всякими-разными околокомпьютерными байками, интересными фактами из истории и рассказами о том, чем я сейчас занимаюсь. Планов у меня громадье, тем набралось уже столько, что хватит еще на целый год как минимум. Так что, "оставайтесь с нами". :-)

Да, конструктивная критика всячески приветствуется.

понедельник, августа 22, 2005

Приведение типов в C++

Лучшая практика по приведению типов: не делать этого. Потому что, если в программе потребовалось приведение типов, значит в этой программе с большой долей вероятности что-то неладно. Для довольно редких ситуаций, когда это все-таки действительно нужно, есть четыре способа приведения типов. Старый, оставшийся со времен C, но все еще работающий, лучше не использовать вовсе. Хотя бы потому, что конструкцию вида (Тип) очень сложно обнаружить при чтении кода программы.

const_cast
Самое простое приведение типов. Убирает так называемые cv спецификаторы (cv qualifiers), то есть const и volatile. volatile встречается не очень часто, так что более известно как приведение типов, предназначенное для убирания const. Если приведение типов не удалось, выдается ошибка на этапе компиляции.
При использовании остальных приведений типов cv спецификаторы останутся как были.

const char *str = "hello";
char *str1 = const_cast<char*>(str);

Updated 20.07.2008
Пример несколько неудачен. Если снимать const с переменной, которая изначально была const, то дальнейшее её использование приведёт к undefined behaviour. Вот хороший пример:
int i;
const int * pi = &i;
// *pi имеет тип const int,
// но pi указывает на int, который константным не является
int* j = const_cast<int *> (pi);


static_cast
Может быть использован для приведения одного типа к другому. Если это встроенные типы, то будут использованы встроенные в C++ правила их приведения. Если это типы, определенные программистом, то будут использованы правила приведения, определенные программистом.
static_cast между указателями корректно, только если один из указателей - это указатель на void или если это приведение между объектами классов, где один класс является наследником другого. То есть для приведения к какому-либо типу от void*, который возвращает malloc, следует использовать static_cast.
int * p = static_cast<int*>(malloc(100));
Если приведение не удалось, возникнет ошибка на этапе компиляции. Однако, если это приведение между указателями на объекты классов вниз по иерархии и оно не удалось, результат операции undefined. То есть, возможно такое приведение: static_cast<Derived*>(pBase), даже если pBase не указывает на Derived, но программа при этом будет вести себя странно.

dynamic_cast
Безопасное приведение по иерархии наследования, в том числе и для виртуального наследования.
dynamic_cast<derv_class *>(base_class_ptr_expr)
Используется RTTI (Runtime Type Information), чтобы привести один указатель на объект класса к другому указателю на объект класса. Классы должны быть полиморфными, то есть в базовом классе должна быть хотя бы одна виртуальная функция. Если эти условие не соблюдено, ошибка возникнет на этапе компиляции. Если приведение невозможно, то об этом станет ясно только на этапе выполнения программы и будет возвращен NULL.
dynamic_cast<derv_class &>(base_class_ref_expr)
Работа со ссылками происходит почти как с указателями, но в случае ошибки во время исполнения будет выброшено исключение bad_cast.

reinterpret_cast
Самое нахальное приведение типов. Не портируемо, результат может быть некорректным, никаких проверок не делается. Считается, что вы лучше компилятора знаете как на самом деле обстоят дела, а он тихо подчиняется. Не может быть приведено одно значение к другому значению. Обычно используется, чтобы привести указатель к указателю, указатель к целому, целое к указателю. Умеет также работать со ссылками.

reinterpret_cast<whatever *>(some *)
reinterpret_cast<integer_expression>(some *)
reinterpret_cast<whatever *>(integer_expression)

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


Что делает приведение типов в стиле С: пытается использовать static_cast, если не получается, использует reinterpret_cast. Далее, если нужно, использует const_cast .

Примеры
unsigned* и int* никак не связаны между собой. Есть правило приведения между unsigned (int) и int, но не между указателями на них. И привести их с помощью static_cast не получится, придется использовать reinterpret_cast. То есть вот так работать не будет:
unsigned* v_ptr;
cout << *static_cast<int*>(v_ptr) <<endl;


Приведение вниз по иерархии:
class Base { public: virtual ~Base(void) { } };
class Derived1 : public Base { };
class Derived2 : public Base { };
class Unrelated { };

Base* pD1 = new Derived1;
Вот такое приведение корректно: dynamic_cast<Derived1 *>(pD1);
А вот такое возвратит NULL: dynamic_cast<Derived2 *>(pD1);
Никак не связанные указатели можно приводить с помощью reinterpret_cast:
Derived1  derived1;
Unrelated* pUnrelated = reinterpret_cast<Unrelated*>(&derived1);

Пример использования static_cast:
int*   pi;
void* vp = pi;
char* pch = static_cast<char*>(vp);

Примеры использования reinterpret_cast:
float f (float);
struct S {
float x;
float f (float);
} s;
void g () {
reinterpret_cast<int *>(&s.x);
reinterpret_cast<void (*) ()>(&f);
reinterpret_cast<int S::*>(&S::x);
reinterpret_cast<void (S::*) ()>(&S::f);
reinterpret_cast<void**>(reinterpret_cast<long>(f));
}

Приведение в стиле C можно использовать, чтобы избавиться от значения, возвращаемого функцией. Польза от этого сомнительная, правда...
string sHello("Hello");
(void)sHello.size(); // Throw away function return
Также я видела использование приведение типов в стиле С для приведения к приватному базовому классу, но для этого можно использовать и reinterpret_cast.

Ссылки по теме:
1996 С++ Drawft Standarts - Expressions
compl.lang.c++.moderated. static_cast vs. reinterpret_cast
borland.public.cppbuilder.vcl.components.using. static_cast, dynamic_cast, reinterpret_cast
compl.lang.c++.moderated. static cast
compl.lang.c++.moderated. New-Style Casts

Technorati tag:

пятница, августа 19, 2005

Анатомия игрового движка

Старая, но тем не менее все еще интересная, статья с ExtremeTech: Game Engine Anatomy 101. Рассказывает о том, что такое игровой движок, чем он отличается собственно от игры и попутно делается обзор всех основных технологий, применяемых в играх. Не знаю как им это все удалось в одну статью уместить, но она и размера получилась внушительного - не статья, а небольшая книга.

Technorati tag:

среда, августа 17, 2005

Выступление Джона Кармака на QuakeCon2005

Updated 12.02.2006

GameSpy опубликовал заметку по мотивам выступления Джона Кармака на последнем QuakeCon. Эдакое изложение для простых смертных. Для непростых смертных есть видеозапись его выступления: 269MB, 94 минуты.

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

Ссылки по теме:
Перевод выступления Кармака на Quakecon 2004

Technorati tag:

понедельник, августа 15, 2005

Приватные чисто виртуальные функции

С первого взгляда такой код может вызвать некоторое удивление:

class CBase
{
private:
virtual void func()=0;
};
Как же так, приватная и чисто виртуальная функция? Ее же нельзя будет переопределить и вызвать из класса-наследника! Еще как можно. Уровень доступа и виртуальность друг от друга не зависят. То есть ее не только можно будет переопределить, ей можно будет сменить уровень доступа, сделать публичной, например...
class CDerived : public CBase
{
public:
virtual void func()
{
...
}
};
В каких ситуациях это может понадобится? Почему не сделать уровень доступа protected? Ну например, вы хотите, чтобы функция была переопределена в наследнике, но чтобы нельзя было вызвать функцию базового класса. Допустим, каждому классу нужен уникальный идентификатор. Тогда логичной выглядит такая иерархия.
class CBase
{
private:
virtual int getID()=0;
...
};

class CDerived : public CBase
{
private:
virtual int getID()
{
//возвращает ID
}
...
};

class CDerived1 : public CDerived
{
prvate:
virtual int getID()
{
//возвращает ID
}
...
};
Если попытаться описать функцию из CDerived1 как
int CDerived1::getID()
{
return CDerived::getID();
}
то компилятор скажет что-нибудь вроде "cannot access private member declared in class 'CDerived' ".

Technorati tag:

суббота, августа 13, 2005

Nether Earth и Tower Toppler

Сейчас по Интернету гуляет известие о выпущенном ремейке классической старой игрушки Nether Earth. Есть исходники и бинарники для WIN32/OSX, Linux RPM. Сайт их сейчас недоступен (все побежали скачивать?), вот еще местечко, откуда новый Nether Earth под Win32 можно скачать.







Ну и раз уж об этом пошел разговор: ремейк еще одной хорошой старой игрушки Tower Toppler. Опять же с исходниками, выложены бинарники для WIN32/BeOS/OSX, есть Suse 9.0 RPM.






Я не очень понимаю тоску по Nether Earth, я в него не играла в Те Светлые Времена. Поэтому вот Слово от Джима, который описывает Как Это Было. Надеюсь, это поможет вам проникнуться духом Тех Времен.

Nether Earth
Это ПЕРВАЯ RTS для компьютеров. Задолго до всяких дюн. Была для ZX Spectrum. Моя любимейшая игра была. Это веха в истории RTS не менее важная, чем ДУМ для FPS... Я часами в нее играл (предварительно минут по 20-30 тратя на загрузку с кассеты с многочисленными сбоями).
Ничерта вы молодые не понимаете. Сейчас вам все легко достается. Слишком легко! Ссылку клик и жди. А мне, чтобы поиграть тогда в игрушку, надо было в 6 утра, зимой к метро, на Тушку, 15 км. в гору... (Там были НЕВЕРОЯТНЫЕ горы из снега, патамушта тротуары не чистили и на них просто наваливали то, что собиралось с шоссе. А еще менты перегораживали подходы турникетами, чтобы народ не скапливался толпами и зигзагами наматывала те самые 15 км. по этим буеракам.) В страшной хаотичной толчее подобраться к кассетам. Руки не чувствуются от мороза. Попытаться сквозь мутный целофан на столе разглядеть под ним кассеты. Купить ее за 20 рублей. Ты хотя бы представляешь сколько другого всего можно было купить на 20 рублей?! Потом переться домой (с Тушки это для меня 1.5 часа) и пытаться побороть качество осыпающейся свемовской кассетной пленки на электронике-512! Рыкая на окружающих, чтобы не ходили и молиться, чтобы не включился холодильник... Помимо холодильника в твоей квартире были еще не менее дебильные совковые холодильники у соседей, которые при пуске давали такой скачек на пакеты, что магнитофон запинался.
На получение удовольствия от одной кассеты с жалкими 10-15 играми уходил МЕСЯЦ! При этом надо было еще бороться с проводами отваливающимися от штекеров, с западающими кнопками на дешевой китайской клавиатуре, которая вообще для станка фрезерного разрабатывалась и т.п.
У вас руль с фидбэком? Так я тебе расскажу чего тогда стоило поиграть в игру не с кнопок, а с хотя бы джойстика...
Сперва ты как ненормальная перлась бы на ту же Тушку в 5 утра, зимой, 15 км. в гору не чувствуя рук. Потом бы ты стояла в длиииинной очереди еще минут 40, чтобы заплатить 1 руб. за вход на рынок. Там бы ты продираясь сквозь толпу таких же окоченевших фриков от прилавка к прилавку искала бы хоть что-то напоминающее джойстик хоть от чего-нибудь. В итоге, через несколько часов броуновского движения ты покупала китайскую поделку для видеоигры в РАЗОБРАННОМ виде. Потому что в собранном он бы стоил 15 рублей, а в разобранном - 11. И мало того, если бы ты просто купила механический джойстик, то тебе бы пришлось повторить весь заход на следующие выходные, патамушта этот бы сломался бы уже через час т.к. нежные китайские дети в них не играют а только любуются и к использованию они не предназначены.
А потом бы ты побродив еще два часа купила бы не механический, а герконовый джойстик. Потом бы ты еше час потратила на затоваривание полусотни запасных герконов патамушта они внутри летят быстрее чем максидатовские пятидюймовые дискеты.
А потом бы ты еще сидела час с паяльником, на морозе, 15 км. в гору патамушта китайцы не знали, что тебе нужен широкий разьем, где на кадую пару контактов джойстика приходится по две жилы ленточного провода, который тоже нужно купить на Тушке.
А Тушка работала только по выходным. И поэтому если ты не потрудилась заранее купить провод, герконы, новое жало для паяльника, полсотни резисторов, жидкую канифоль и припой, то тебе пришлось бы ждать еще целую НЕДЕЛЮ патамушта это больше негде было купить.
А потом ты бы, загрузив игру через час шаманства с отверткой, постоянно подкручивая головку магнитофона, чтобы добиться нужной частоты считывания с многочисленными обрывами и матюками, все равно бы не смогла джойстиком пользоваться патамушта игра его НЕ ПОДДЕРЖИВАЕТ. Но ты про это знать не могла никак, патамушта не было интернета чтобы почитать про то во что ты собираешся поиграть. Мало того, ты даже заранее не знаешь как ЭТО выглядит, какого оно жанра и про что, патамушта кроме списка из десятка названий игр на китайско-русско-английском языке больше ничего нет.
Вот так нам жилось 10-15 лет назад. И смайликов мы тогда не знали.
А теперь я буду играть в Nether Earth, ронять скупые мужские слезы и утирать скупые мужские сопли, вспоминать какой молодой я был и как прогнил окружающий мир полный такой неблагодарной все осмеюва...осмие... осмеивающей молодежи!

Если есть такое желание, то свое глубокое восхищение прочитанным можно выразить на форуме Джима.

среда, августа 10, 2005

DARPA Grand Challenge

DARPA Grand Challenge - это соревнования роботизированных машин. DARPA (Defense Advanced Research Projects Agency, Отдел перспективных разработок Министерства обороны США) впервые провел свои соревнования в 2004 году...
Да, не спешите жать на ссылку. DARPA фильтурет свои адреса от пользователеей некоторых стран, включая Россию. Поэтому посмотреть их страничку напрямую сокрее всего не получится. Зато получится накривую: через анонимный прокси. Можно еще www.darpa.mil из Гугловского кэша выковырять.
Итак, основная идея соревнований такова: чтобы не терять бойцов, нужно воевать роботами. Чтобы стимулировать разработки в этой области, нужно проводить соренования. Полностью автономной машине предлагается преодолеть трассу длинной не более 175 миль, проложенную в пустыне, при этом нужно уложиться в 10 часов. В машину загружается трасса и эта самая машина, минуя препятствия, природные и рукотворные, должна добраться до финиша. Кто приедет первым, получит ценный приз: в прошлом году это был 1 млн. долларов, в этом уже 2. Соревнования этого года пройдут в октябре, участвовать в них могут только жители США. В прошлом году никто до финиша не добрался, так что на этот год возлагаются большие надежды.

Ссылка по теме:
Страничка команды-участницы соревнований, Robomonster.


Technorati tag:

пятница, августа 05, 2005

Что будет, если поставить текстуру в NULL?

Это вольный перевод поста What happens when I set a texture to NULL?. Свои комментарии я указала в квадратных скобочках.

Вообще возможны варианты. Есть то, что должно случиться; то, что случается часто; то, что случается еще чаще и чего следует ожидать. Так что давайте по порядку.

Что должно случиться
Согласно исходниками референсного растеризатора [речь идет о программе, которая входит в состав DirectX SDK и делает софтверный рендеринг точно так, как это подразумевает API без каких-либо оптимизаций и т.п.], если вы читаете какой-либо тексел из NULL-текстуры, вы должны получить непрозрачный черный. То есть RGB=0.0, а alpha=1.0.
Однако, если вы используете конвейер TextureStageState [TSS] (то есть не пиксельный шейдер), и D3DTS_ARG1 установлен в D3DTA_TEXTURE, то этот TSS устанавливается в D3DTOP_DISABLE и это означает, что и все последующие шаги (в смысле TSS'ы) будут пропущены. Обратите внимание, что будет, если ARG2 установлен в TEXTURE - тогда вы получаете непрозрачный черный и на этом шаге делается то, что и должно было делаться. Несколько странно, но уж как есть.
Помните - это согласно вышеупомянутым исходникам и, в теории, это есть официальная точка зрения. Но вы читайте дальше...

Что случается часто
Некоторые карточки ту часть, что касается непрозрачного черного, воспринимают правильно, но игнорируют ту часть, что касается отключения TSS. То есть вы получите нормальную работу TSS.

Что случается еще чаще
Большинство карт следуют стандарту OpenGL, который это трактует как непрозрачный белый. И также игнорируется часть относительно TSS. [Я видела такое поведение на GeForce4 и GeForce6800.]
Еще одно часто встречающееся поведение - случайные данные в текстуре.

Чего следует ожидать
Ничего. То есть вообще ничего. Спецификация D3D менялась несколько раз, драйверы меняются постоянно, различные производители все делают по-разному, и, честно говоря, случиться может все, что угодно. Результат установки текстуры в NULL действительно может быть непредсказуем. Лучший совет: не делайте этого. Пишите свои пиксельные шейдеры и TSS'ы, чтобы они делали только то, что должны и ничего более. Если это значит, что вам нужны другие шейдеры/TSS'ы на случай NULL-текстуры, напишите их. Обычно эти шейдеры/TSS'ы будут работать гораздо быстрее, чем использующие NULL-текстуру и точно будут более предсказуемы.


Technorati tag: