Архивы по Категориям: C++

Время кидаться пальцами

Вышла новая версия буста. Теперь могу обоснованно заявлять, что в бусте присутствует и мой contribution в том числе.

 

Докатился…

Отправил баг-репорт в boost.

Будут молчать, придется пойти на совсем немыслимое — отправить им патч.

Программерское

Вчера скачал 2012 студию. Официально, с MSDN. Уже 10 минут жду первого запуска.

А ещё сегодня гугол заявил, что ридер закрывают. Пичалько. И ведь ясно, что бизнес оппортьюнити и все такое, да подъем аналога, который не будет являться очередным УГ, под силу разве что компании с сопоставимым количеством дензнаков.

Шаблонная магия

Нечаянно немножко шаблонно наметопрограммировал в текущем проекте. Чтоп компилятор за меня мою работу делал, да.

Теперь главное — не оставить домашнего адреса, когда увольняться буду.

Это они серьезно?

На фоне якобы запуска якобы Самсунгом новой версии Tizen, на КЫВТе состоялось обсуждение API. Я тоже побыл соучаснегом.

All two-phase construction classes have the Construct() method, which must be called once right after the class is instantiated.

Two-phase construction is used to let the caller know about an exception raised in class constructors. Because Tizen does not use the standard C++ exception mechanism in the platform layer, it is impossible to let the caller know about exceptions raised in the class constructors

Это пипец.

Error handling in Tizen works differently compared to standard C++. Tizen uses error results instead of C++ exceptions, due to historical reasons.

All exceptions in Tizen are caught as the result return type. The E_SUCCESS result indicates a method succeeded, while all other result values indicate an error.

А это треш и угар. Due to historical reasons, ага. В 2013 году. Они ее во времена первых версий DOS разрабатывать начали, что ли?

Весь хелп здесь. Примеры использования доставляют до состояния «дайте мне ЭТО развидеть».

Зато авторы ЭТОГО, надеюсь, в сортировках разбираются и гномиков в боингах взвешивать умеют.

Warning C4103 in Visual Studio 2010 is broken?

В продолжение поста.

Есть такой хедер

// dummyHeader.h
#pragma  pack(push, 1)
			struct Dummy
			{
				int a;
				char b;
				short c;
			};
// oops, #pragma pack(pop) is missing..

И есть такой cpp файл, его включающий

#include "DummyHeader.h"

#include "SomeOtherHeader.h"

При компиляции такого исходника студия честно жалуется

warning C4103: ‘test.cpp’ : alignment changed after including header, may be due to missing #pragma pack(pop)

Теперь следим за руками:

#include "SomeOtherHeader.h"
#include "DummyHeader.h"

Поменяли хедеры местами.

Компилирует — аж фуфайка заворачивается. Никаких ворнингов.

Опытным путем выяснил, что предупреждение выдается только в одном случае — когда «плохой» заголовок стоит в самом верху списка.

Это ж лютый песец, йащетаю.

ЗЫ:
Microsoft Visual Studio 2010
Version 10.0.30319.1 RTMRel

ЗЫЫ:
Проблема не наблюдается в SP1Rel. Там предупреждение выдается в любом случае

Что pragma pack в себе таит

Сегодня коллега порвал три бубна. Искал страшный и ужасный баг, который приводил к тому, что в отладочной сборке срабатаывал run-time check на heap corruption. Порвав последний бубен, коллега воззвал к нашему гуру отладки, одним взглядом исцеляющему stack overflow и access violation. Гуру пришел со своими бубнами. Когда и у него бубны кончились, они зачем-то стали просить бубен взаймы у меня.

Вкратце, происходило следующее. Есть примерно такой класс

class Response
{
#pragma pack(push, 1)
struct Header
{
int blah;
unsighed short blahblah;
unsigned char vesrion;
}
#pragma pack(pop)
public:
 Response();
 void firstMethod();
 virtual void anotherMethod();
private:
 Header m_header;
 std::vector<char> m_data1;
 std::vector<char> m_data2;
}

Это часть парсера одного абстрактного протокола в вакууме, что объясняет использование #pragma pack(1).

Итак, отладочный рантайм повадился выдавать страшное предупреждение о повреждении кучи при удалении объекта такого класса. Интересно, что в релизе никаких побочных эффектов, характерных для heap corruption, не было (отладочные проверки там, понятное дело, отключены, но хороший расстрел памяти шилом в мешке не упрячешь, он всегда наружу выйдет) .

Ребята раскопали, как работает подобная проверка повреждения кучи. Всем известно, что отладочный рантайм MSVC инициализирует выделенную, но еще неинициализированную память значениями 0xCD. Но вот мне было интересно узнать, что в дополнении к этому, рантайм также расставляет заборы. То есть при выделении памяти под объект размера X рантайм размещает значение 0xFD по смещению X от начала объекта или, другими словами, сразу «за» объектом (тут подробнее). При удалении объекта рантайм проверяет, цел ли забор, то есть сохранилось ли значение 0xFD сразу за удаляемым объектом. Сообщение, выдаваемое в случае, если за объектом обнаружено нечто неожиданное, но не 0xFD, выглядит настолько устрашающе, что при его виде плачут даже лично знакомые с Александреску.

cdcdcdcd cdcdcdcd fdcdcdcd   //Allocated, not initialized
00abed01 6fa91001 fdcdcdcd   // Correctly initialized
00abed01 6fa91001 00cdcdcd   // BANG!

Первая строка наверху — память выделена, но не инициализирована (конструктор объекта еще не выполнился). Следующая строка — память инициализирована, объект сконструирован, замечаний нет. На третьей линии случился какой-то катаклизм и забор (0xFD) оказался продырявлен. Такое может произойти, к примеру, при buffer overrun. При удалении такого объекта отладочный рантайм поднимет вой.

Очевидно, что эта проверка довольно наивна и бессильна против «настоящего» расстрела памяти, когда стреляют прямой наводкой по живым объектам, не задевая промежутков между ними.

Вернемся к нашим баранам. Buffer overrun мы отмели ввиду отсутствия такового (буфера). Для случайного расстрела памяти мина больно прицельно попадала в одну и ту же штакетину забора, поэтому вариант снайпера в соседнем потоке тоже был отметен. Осталось смотреть на создаваемый объект под отладчиком.

После бесчисленных экспериментов удалось установить, что несмотря на то, что sizeof(Response), вызванная в месте создания экземпляра объекта, выдавала 59 байтов, при пошаговой отладке код конструктора объекта ожидал, что объект имеет размер в 60 байтов. Как результат — последний член класса «выезжал» за пределы отведенной под отбъект памяти ровно на 1 байт, чем и рушил забор.

По правде сказать, хорошо поднаторевший в поисках багов Шрёдингера Штирлиц должен был бы насторожиться при виде оператора sizeof, возвращающего 59. Но в нашем случае причина чертовщины оказалась в том, что в разных единицах трансляции размер класса оказался разным! В итоге в месте использования код думал, что имеет дело с объектом размером в 59 байт, в то время как код конструктора полагал, что объект выровнен на границу 4 байт и посему имеет размер 60 байт.

Причина такого поведения оказалась проста. В одном из заголовков #pragma pack(push, 1) перед структурой поставили, но #pragma pack(pop) — забыли. Этот заголовок оказался включен в месте использования, и как результат — в этом *.cpp все структуры оказались выровнены на границу 1 байта. Багфикс заключался в поиске непарной прагмы и восстановлении статус-кво.

Весьма впечатляющий по своей простоте причины при запутанности спецэффектов баг. И что интересно, непонятно, как можно обезопаситься от такой ерунды в случаях, когда использования #pragma pack(1) избежать нельзя.

В продолжение — universal references

В продолжение вчерашней темы - universal references in C++ by Scott Meyers.

Ох и крови мне в свое время попили эти ссылки на ссылки в шаблонных функциях!

Короче, теперь вот это

const someData& getData();

template 
void DoSomething(const T&& _what)
{

}

DoSomething(getData());   // C++03: Shit!

должно компилироваться нормально, слава reference collapsing!

Это, конечно, далего не главная фича reference collapsing, но ИМХО уже одного этого достаточно, чтобы знать и любить. Главное — помнить, что collapsing работает только в случае, когда тип выводится компилятором

Профессиональное

Говорят, что по-настоящему понимаешь предмет, когда становишься способен доходчиво объяснить его.

В современном C++ появились rvalue references. Что это такое, в принципе понятно. Как это объяснить на пальцах — х.з.

Примерно такая же история с корутинами. Этот концепт настолько прост, что я всегда попадаю в тупик, пытаясь объяснить его в двух словах. Хотя конечный автомат внутри корутины — это интересно (правда, расточительно — целый стек пропадает).

О туннельном мышлении

Когда у тебя в руках молоток, то вокруг всё гвозди мерещатся.
Народная мудрость

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

Бывает так, что человек, очень часто весьма опытный профессионал, блестяще знающий теорию и порой могущий назвать все параметры практически любой функции из WinAPI*, в реальной разработке ограничивает себя неким весьма небольшим подмножеством подходов, методик и парадигм. Большинство из нас делают это так или иначе, у нас всех есть свои любимые паттерны, отработанные методики, привычная цветовая схема в редакторе, в конце концов. Но то, что мне приходится время от времени видеть, не может быть объяснено просто силой привычки или наработанной методикой.

Этот пост посвящен вовсе не хрестоматийному примеру новоиспеченного кодера, который ночью прочитал книжку «Банды Четырех» и с утра принялся решать все проблемы проекта внедрением всех паттернов сразу. Такой максимализм, как правило, юношеский, обычно проходит с факториалом от количества набитых шишек. Я же говорю о том, что весьма часто случается так, что вроде бы довольно неглупый человек начинает носиться со своим любимым граалем и перестает видеть мир вокруг. Причем зачастую подобная ситуация может проявиться только в каком-то одном конкретном случае.

У меня в практике был эпизод. Планировали мы новый модуль для системы. С самого начала было очевидно, что нам нужно будет изолировать фронт-энд от бэк-энда, то есть мы должны будем иметь как минимум два потока. Обычно в подобных проектах нам удавалось обойтись без многопоточности, на в данном конкретном случае это был единственный вариант. На слове «многопоточность» технического лидера (на тот момент) нашей команды конкретно заклинило.

Оказалось, что раз наш модуль многопоточный, нам совершенно необходима lock-free очередь. И даже не только очередь, а lock-free контейнеры (как потом оказалось, построенные на основе этой самой очереди). Причем очередь оказалась нужна еще до начала обсуждения архитектуры. Более того, оказалось, что обсуждать архитектуру совершенно не надо, ибо lock-free контейнеры суть решение всех проблем и нам надо всего лишь их реализовать и сразу наступит вселенское Щастье с большой буквы «H» (потому что по-английски). С этими словами он и кинулся эти самые очереди писать/выковыривать из других проектов. Да, прямо посередине первого обсуждения архитектуры. Еще до того, как мы определились даже не с фичами, а с требованиями (про stories и таски я даже не заикаюсь).

Все мои последующие попытки вызвать хоть какую-нибудь дискуссию об архитектуре заканчивались монологом о том, что нам не архитектуру архитектурить надо, а лок-фри конейнеры писать. Дескать, мы сейчас их напишем, а потом вообще за полчаса долетим. Якобы что можно будет не думать о блокировках и свободно вставлять данные из одного потока в контейнер другого, и посему можно вообще больше ничем на заморачиваться. Так на свет появились лок-фри очередь, лок-фри список и даже лок-фри ассоциативный контейнер. С «продуманным и удобным интерфейсом».

Результат оказался довольно предсказуем. Дикий срыв сроков, треш и угар в архитектуре, лингвини** в коде, чертовщина в багзилле, нервный тик у менеджера проекта. Вообще весь проект со стороны напоминал попытку впихнуть прямоугольную пробку в треугольную дырку. Ваш покорный слуга потом это рефакторил. Конейнеры эти в итоге оказались не нужны и были выброшены. В паре мест получил где-то 1000-кратное увеличение производительности просто за счет применения правильной архитектуры, еще в нескольких местах убрал несколько алгоритмов с якобы O(log N), а на деле хорошо если O(N) и заменил их на O(1).

В процессе рефакторинга, который скорее напоминал вскрытие, выяснилось, что весь дизайн программы своидлся к грубому приколачиванию модных lock-free гвоздями туда, где в них не было никакой нужды. Простейший анализ data flow показал, что обмен данными между потоками минимален и направлен в одну сторону, и применение lock-free контейнеров просто не давало никакой выгоды. Ну хотелось человеку их применить, и все тут. Более того, передовые лок-фри контейнероы оказались не thread safe в некоторых применениях, как бы ни смешно это звучало. Архитектура же модуля была представлена кучей строительного мусора, местам скрепленного гвоздями (жидкими). Вкупе с отстутствием юнит-тестов (стандартное — тесты писать некогда, копать надо!) рефакторинг этого превратился в увлекательный квест.

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

О результатах такого подхода можете догадаться сами.

*шутка
**лингвини — вид макаронных изделий. Представляет собой толстые, но слегка зауженные феттучини***
***феттучини — это широкие спагетти
4 посетителей онлайн
0 гостей, 4 bots, 0 зарегистрированных
Максимум сегодня:: 23 в 03:25 am UTC
В этом месяце: 95 в 07-08-2014 05:56 am UTC
В этом году: 95 в 07-08-2014 05:56 am UTC
За все время: 95 в 07-08-2014 05:56 am UTC