Написание на C и C++ 64-битных программ, работающих с большими массивами данных, требует особой аккуратности. В 64-битном коде встречаются особые трудноуловимые ошибки, о которых редко рассказывают в книгах или на конференциях. Автор рассмотрит паттерны 64-битных ошибок и даст рекомендации, как писать код, защищённый от них.
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Platonov Sergey
За время работы над проектом был разработан удобный фреймворк для написания асинхронного кода. В докладе будет рассмотрено то, как он устроен и как со временем эволюционировал. Разберемся, как с помощью наших примитивов решать проблемы, часто встречающиеся в асинхронном программирование; будут примеры удачного и неудачного использования. Отдельно остановимся на сравнении получившихся средств с тем, что было добавлено в С++11.
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Yandex
Основываясь на опыте разработки Крипты, Дмитрий рассмотрит средства реализации статического и динамического полиморфизма в C++, а также некоторые их паттерны и антипаттерны.
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Yandex
Флеш-накопители используются в самых разных устройствах, от мобильных телефонов до компьютеров и серверов. Для каждой модели накопителя нужна прошивка с определённым набором параметров, которые могут отличаться в зависимости от ситуации. В докладе будет описан универсальный фреймфорк на С++, который предоставляет разработчикам симуляторов простой, прозрачный и быстрый доступ к любому параметру. Тестировщикам же он позволяет управлять конфигурациями при помощи стандартных инструментов редактирования и слияния.
Написание на C и C++ 64-битных программ, работающих с большими массивами данных, требует особой аккуратности. В 64-битном коде встречаются особые трудноуловимые ошибки, о которых редко рассказывают в книгах или на конференциях. Автор рассмотрит паттерны 64-битных ошибок и даст рекомендации, как писать код, защищённый от них.
Павел Сушин «Асинхронное программирование на С++: callbacks, futures, fibers»Platonov Sergey
За время работы над проектом был разработан удобный фреймворк для написания асинхронного кода. В докладе будет рассмотрено то, как он устроен и как со временем эволюционировал. Разберемся, как с помощью наших примитивов решать проблемы, часто встречающиеся в асинхронном программирование; будут примеры удачного и неудачного использования. Отдельно остановимся на сравнении получившихся средств с тем, что было добавлено в С++11.
Некоторые паттерны реализации полиморфного поведения в C++ – Дмитрий Леванов,...Yandex
Основываясь на опыте разработки Крипты, Дмитрий рассмотрит средства реализации статического и динамического полиморфизма в C++, а также некоторые их паттерны и антипаттерны.
Использование шаблонов и RTTI для конфигурации симулятора флеш-накопителя - Г...Yandex
Флеш-накопители используются в самых разных устройствах, от мобильных телефонов до компьютеров и серверов. Для каждой модели накопителя нужна прошивка с определённым набором параметров, которые могут отличаться в зависимости от ситуации. В докладе будет описан универсальный фреймфорк на С++, который предоставляет разработчикам симуляторов простой, прозрачный и быстрый доступ к любому параметру. Тестировщикам же он позволяет управлять конфигурациями при помощи стандартных инструментов редактирования и слияния.
ЛЕКЦИЯ 5. Шаблоны многопоточного программирования
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 3. Реентерабельность. Сигналы. Локальные данные потоков. Принудительное завершение потоков
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 4. Стандарт POSIX Threads. Реентерабельность функций. Обработка сигналов. Локальные данные потоков. Принудительное завершение потоков. Шаблоны программирования с использованием потоков
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...Alexey Paznikov
ЛЕКЦИЯ 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
Программисты часто работают с числами. Чаще всего это целые числа, но иногда доводится работать и с дробными. C этими самыми числами приходится делать разные операции: сложение, умножение, приведение типов, сравнение, округление и многие другие. Увы, далеко не все до конца понимают, как же именно компьютер совершает все эти замечательные операции. В этом докладе мы с вами прорешаем серию увлекательных упражнений на знание арифметики. Поговорим про стандарт IEEE 754, про разницу в рантаймах и компиляторах, про регистры FPU и прочие сложности жизни.
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“Platonov Sergey
Предлагаемый подход позволяет без труда получить параллельную асинхронную обработку данных без явного использования средств синхронизации, по максимуму задействуя доступные вычислительные ресурсы. Использование сопрограмм значительно упрощает написание многопоточного кода. Это дает возможность сконцентрироваться непосредственно на задаче обработки данных, не занимаясь вопросами синхронизации различных операций, включая асинхронную работу с сетью.
Продолжаем говорить о микрооптимизациях .NET-приложенийAndrey Akinshin
Этот доклад продолжает тему моего выступления с прошлого DotNext про сложную науку о микрооптимизациях. Вас ждут новые увлекательные истории о том, что же происходит под капотом .NET-программ. Будем обсуждать различия разных C# и JIT компиляторов (Roslyn и RyuJIT в том числе), медитировать на IL и ASM листинги, а также разбираться с особенностями современных CPU.
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...Alexey Paznikov
ЛЕКЦИЯ 6. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель памяти C++
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
В последнее время в промышленной разработке ПО особую популярность обретают Domain-Specific Lanugages (DSL). Они драматически упрощают разработку и дают возможность “программировать” не только программистам, но и пользователям прикладных программ.
В своем докладе я расскажу об опыте использования DSL применительно к С++, причем упор будет сделан на производительность кода DSL, и его мгновенную “встраиваемость” в запущенную программу путем компиляции DSL-кода в нативный код с помощью инструментария LLVM.
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...Alexey Paznikov
ЛЕКЦИЯ 6. Разработка параллельных структур данных на основе блокировок
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
Зачастую, знакомство с алиасингом в C++ у многих программистов начинается и заканчивается одинаково: -fno-strict-aliasing. На вопросы новичка, более опытные коллеги отвечают в стиле: «не трогай! а то все сломаешь!». Новичок и не трогает. В докладе будет предпринята попытка заглянуть под капот и понять, что же там, внутри. Что такое алиасинг, где он может быть полезен и какие реальные преимущества дает. Тема будет рассмотрена и со стороны программиста и со стороны разработчика компилятора. А по сему, вопрос «зачем?» будет центральным в повествовании.
ЛЕКЦИЯ 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек: проблема ABA, указатели опасности, сборщики мусора, счётчик ссылок, применение модели памяти С++.
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 5. Шаблоны многопоточного программирования
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 3. Реентерабельность. Сигналы. Локальные данные потоков. Принудительное завершение потоков
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
ЛЕКЦИЯ 4. Стандарт POSIX Threads. Реентерабельность функций. Обработка сигналов. Локальные данные потоков. Принудительное завершение потоков. Шаблоны программирования с использованием потоков
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
ПВТ - осень 2014 - Лекция 5 - Многопоточное программирование в языке С++. Р...Alexey Paznikov
ЛЕКЦИЯ 5. Многопоточное программирование в языке С++. Работа с потоками. Защита данных. Синхронизация. Будущие результаты
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
Программисты часто работают с числами. Чаще всего это целые числа, но иногда доводится работать и с дробными. C этими самыми числами приходится делать разные операции: сложение, умножение, приведение типов, сравнение, округление и многие другие. Увы, далеко не все до конца понимают, как же именно компьютер совершает все эти замечательные операции. В этом докладе мы с вами прорешаем серию увлекательных упражнений на знание арифметики. Поговорим про стандарт IEEE 754, про разницу в рантаймах и компиляторах, про регистры FPU и прочие сложности жизни.
Григорий Демченко, “Асинхронность и сопрограммы: обработка данных“Platonov Sergey
Предлагаемый подход позволяет без труда получить параллельную асинхронную обработку данных без явного использования средств синхронизации, по максимуму задействуя доступные вычислительные ресурсы. Использование сопрограмм значительно упрощает написание многопоточного кода. Это дает возможность сконцентрироваться непосредственно на задаче обработки данных, не занимаясь вопросами синхронизации различных операций, включая асинхронную работу с сетью.
Продолжаем говорить о микрооптимизациях .NET-приложенийAndrey Akinshin
Этот доклад продолжает тему моего выступления с прошлого DotNext про сложную науку о микрооптимизациях. Вас ждут новые увлекательные истории о том, что же происходит под капотом .NET-программ. Будем обсуждать различия разных C# и JIT компиляторов (Roslyn и RyuJIT в том числе), медитировать на IL и ASM листинги, а также разбираться с особенностями современных CPU.
ПВТ - осень 2014 - Лекция 6 - Атомарные операции. Внеочередное выполнение инс...Alexey Paznikov
ЛЕКЦИЯ 6. Атомарные операции. Внеочередное выполнение инструкций. Барьеры памяти. Семантика захвата-освобождения. Модель памяти C++
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
преподаватель:
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
Юрий Ефимочев, Компилируемые в реальном времени DSL для С++ Sergey Platonov
В последнее время в промышленной разработке ПО особую популярность обретают Domain-Specific Lanugages (DSL). Они драматически упрощают разработку и дают возможность “программировать” не только программистам, но и пользователям прикладных программ.
В своем докладе я расскажу об опыте использования DSL применительно к С++, причем упор будет сделан на производительность кода DSL, и его мгновенную “встраиваемость” в запущенную программу путем компиляции DSL-кода в нативный код с помощью инструментария LLVM.
ПВТ - весна 2015 - Лекция 6. Разработка параллельных структур данных на основ...Alexey Paznikov
ЛЕКЦИЯ 6. Разработка параллельных структур данных на основе блокировок
Курс "Параллельные вычислительные технологии" (ПВТ), весна 2015
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
Дмитрий Кашицын, Троллейбус из буханки: алиасинг и векторизация в LLVMSergey Platonov
Зачастую, знакомство с алиасингом в C++ у многих программистов начинается и заканчивается одинаково: -fno-strict-aliasing. На вопросы новичка, более опытные коллеги отвечают в стиле: «не трогай! а то все сломаешь!». Новичок и не трогает. В докладе будет предпринята попытка заглянуть под капот и понять, что же там, внутри. Что такое алиасинг, где он может быть полезен и какие реальные преимущества дает. Тема будет рассмотрена и со стороны программиста и со стороны разработчика компилятора. А по сему, вопрос «зачем?» будет центральным в повествовании.
ЛЕКЦИЯ 7. Многопоточное программирование без блокировок. Модель потребитель-производитель. Потокобезопасный стек: проблема ABA, указатели опасности, сборщики мусора, счётчик ссылок, применение модели памяти С++.
Курс "Параллельные вычислительные технологии" (ПВТ), осень 2014
Сибирский государственный университет телекоммуникаций и информатики
Пазников Алексей Александрович
к.т.н., доцент кафедры вычислительных систем СибГУТИ
http://cpct.sibsutis.ru/~apaznikov
200 open source проектов спустя: опыт статического анализа исходного кодаAndrey Karpov
Одна из особенностей работы нашей команды — анализ большого количества различных программных проектов. Рассказывать о закрытых коммерческих проектах часто запрещает NDA, а вот об open source можно и нужно говорить. Какие ошибки допускают в open-source-проектах? Какой код более качественный — закрытый или открытый? Нужно ли придерживаться стандартов кодирования, или они давно устарели? Какие ошибки сложнее найти и исправить — сложные архитектурные или простые опечатки? Проанализировав за несколько лет сотни программных проектов от zlib до Chromium, мы готовы поделиться своим опытом и ответить на эти вопросы.
Павел Беликов, Как избежать ошибок, используя современный C++Sergey Platonov
Одной из проблем C++ является большое количество конструкций, поведение которых не определено или просто неожиданно для программиста. С такими ошибками мы часто сталкиваемся при разработке статического анализатора кода. Но, как известно, лучше всего находить ошибки ещё на этапе компиляции. На этом докладе мы поговорим о том, какие техники из современного C++ позволяют писать не только более простой и выразительный, но и безопасный код. Вы увидите ошибки в коде различных Open Source проектов и узнаете, как можно их избежать, используя новые стандарты
Надеемся, вы уже успели отдохнуть от 13 урока и теперь сможете рассмотреть еще один важный паттерн ошибок, связанный с арифметическими выражениями, в которых участвуют типы различной размерности.
Андрей Карпов
Вы узнаете, что такое статический анализ кода и историю его развития. Узнаете, как эффективно применять инструменты статического анализа в своей работе, увидите практические примеры использования этой методологии. Доклад ориентирован на программистов, использующих языки Си/Си++, но будет полезен всем
Использование C++ для низкоуровневой платформозависимой разработки — Кирилл ...Yandex
Доклад посвящён преимуществам и недостаткам C++ в сравнении с C и ассемблером в контексте разработки критичных к производительности библиотек, использующих специфичные для платформы возможности. На примерах реализации оптимизированных компонентов аудио- и видеокодеков мы рассмотрим, как можно упростить исходный код с помощью нестандартных расширений компилятора и новых возможностей C++11. Внимание также будет уделено функциональности, которой не хватает в текущих реализациях компилятора. В качестве целевых платформ используются ARMv7+NEON/ARM64, но предварительного знакомства с ними не требуется.
Мы закончили рассмотрение паттернов 64-битных ошибок. Последнее на чем мы остановимся в связи с этими ошибками, является то, как они могут проявляться в программах.
Статический и динамический полиморфизм в C++, Дмитрий ЛевановYandex
На примере некоторых архитектурных решений Крипты Дмитрий расскажет о способах реализации полиморфного поведения в программах на C++, о преимуществах и недостатках этих способов, а также о новых возможностях C++11.
Мы все допускаем ошибки при программировании и тратим массу времени на их устранение.
Один из методов который позволяет быстро диагностировать дефекты – статический анализ исходного кода.
Языки C, C++ и C++0x как набор ножей по дереву. С их помощью создаются великолепные изделия, но немного неаккуратности и можно глубоко порезаться. Одной из самых ранних методик обнаружения ошибок в коде программ является статический анализ кода. Запуская анализ сразу после написания нового кода или во время ночных сборок, можно выявить множество ошибок еще до этапа тестирования. Это сокращает стоимость и время их исправления. Также могут быть обнаружены дефекты, редко проявляющие себя, которые могут являться головной болью на протяжении многих месяцев сопровождения программы.
В докладе будет продемонстрировано множество примеров ошибок в известных open source программах и библиотеках, которые можно обнаружить с помощью статических анализаторов.
PVS-Studio, решение для разработки современных ресурсоемких приложенийTatyanazaxarova
Инструмент PVS-Studio
набор правил Viva64 для анализа 64-битных приложений;
набор правил VivaMP для анализа параллельных приложений;
набор правил для анализа общего назначения.
Лицензионная и ценовая политика PVS-Studio
Информация о компании ООО «СиПроВер»
Статический анализ и написание качественного кода на C/C++ для встраиваемых с...Andrey Karpov
Как учит печальный опыт множества инцидентов, требования к качеству программного кода встраиваемых систем намного выше, чем для классического программного обеспечения (игры, офисные пакеты, графические редакторы и т.д.). Поговорим о стандартах кодирования, таких как MISRA, SEI CERT, и инструментах статического анализа кода. Статические анализаторы решают две смежные, но всё-таки разные задачи: поиск ошибок и поиск запахов кода. Оба эти направления важны и должны использоваться. Однако часто эти два направления смешивают, что приводят к недоразумениям и отторжению методологии статического анализа кода. Попробуем расставить точки над i.
Надежный обмен данными в гетерогенной среде, между разными платформами и технологиями является одним из ключевых моментов разработки сложных систем. Во время обмена данные преобразуются в некоторый промежуточный формат совместимый между платформами. Преобразование в подобный формат и из него — крайне рутинная и подверженная ошибкам работа.
Метаописание данных неким декларативным языком с последующей автогенерацией типизированных структур облегчает жизнь разработчику. Снимает с него необходимость задумываться о промежуточном формате, о правильном порядке полей, а типизированность гарантирует выявление ошибок еще на этапе компиляции кода.
В докладе будет рассмотрено несколько подобных решений, их плюсы и минусы. Также будет рассмотрен с практической стороны наш собственный формат метаданных, используемый нами на протяжении более 5 лет.
Целевая аудитория:
Ограничений нет, но в большей степени разработчики, имеющие уже определенный опыт разработки разнородных систем или приступающие к подобной задаче.
Многие разработчики не представляют, как дорого обходятся ошибки в программах. Причем я имею в виду не падения ракет и прочие катастрофы, а обыкновенное прикладное программное обеспечение. Хочется показать всю важность нахождения ошибок на самых ранних этапах. Одним из способов выявить ошибку как можно раньше является статический анализ кода. Поговорим мы не только об этом, но и о различных приемах при написании кода, которые позволят избежать множество типовых ошибок.
В языке Си можно использовать функции без их предварительного объявления. Обращаю внимание, что речь идет именно о языке Си, а не Си++. Конечно, данная возможность опасна. Рассмотрим связанный с ней интересный пример 64-битной ошибки.
Similar to Проблемы 64-битного кода на примерах (20)
Урок 27. Особенности создания инсталляторов для 64-битного окруженияTatyanazaxarova
При разработке 64-битной версии приложения дополнительное внимание стоит уделить и вопросу дистрибуции программы. Ведь при установке на 64-битной операционной системе есть несколько нюансов, забыв о которых можно получить неработающий инсталляционный пакет.
После компиляции программы в 64-битном режиме она начинает потреблять большее количество памяти, чем ее 32-битный вариант. Часто это увеличение почти незаметно, но иногда потребление памяти может возрастать в 2 раза.
Урок 25. Практическое знакомство с паттернами 64-битных ошибокTatyanazaxarova
Данная статья содержит различные примеры 64-битных ошибок, собранные в демонстрационном примере PortSample. Однако, начиная с версии PVS-Studio 3.63, вместо PortSample в дистрибутив PVS-Studio включается более новая версия примеров, которая называется OmniSample. Поэтому некоторые скриншоты в статье не соответствуют актуальному состоянию дел.
Сам по себе рост размера структур не является ошибкой, но может приводить к потреблению необоснованного количества памяти и в результате к замедлению скорости работы программы. Будем рассматривать данный паттерн не как ошибку, но как причину неэффективности 64-битного кода.
Процессоры работают эффективнее, когда имеют дело с правильно выровненными данными. А некоторые процессоры вообще не умеют работать с не выровненными данными.
Генерирование и обработка исключений с участием целочисленных типов не является хорошей практикой программирования на языке Си++. Для этих целей следует использовать более информативные типы, например классы, производные от класса std::exception.
Урок 19. Паттерн 11. Сериализация и обмен даннымиTatyanazaxarova
Важным элементом переноса программного решения на новую платформу является преемственность к существующим протоколам обмена данными. Необходимо обеспечить чтение существующих форматов проектов, осуществлять обмен данными между 32-битными и 64-битными процессами и так далее.
Урок 16. Паттерн 8. Memsize-типы в объединенияхTatyanazaxarova
Особенностью объединения (union) является то, что для всех элементов (членов) объединения выделяется одна и та же область памяти, то есть они перекрываются. Хотя доступ к этой области памяти возможен с использованием любого из элементов, элемент для этой цели должен выбираться так, чтобы полученный результат был осмысленным.
Большое количество ошибок при миграции на 64-битные системы связано с изменением соотношения между размером указателя и размером обычных целых. В среде с моделью данных ILP32 обычные целые и указатели имеют одинаковый размер. К сожалению, 32-битный код повсеместно опирается на это предположение. Указатели часто приводятся к int, unsigned, long, DWORD и другим неподходящим типам.
Мы специально выбрали номер "тринадцать" для этого урока, поскольку ошибки, связанные с адресной арифметикой в 64-битных системах, являются наиболее коварными. Надеемся, число 13 заставит вас быть внимательнее.
Урок 10. Паттерн 2. Функции с переменным количеством аргументовTatyanazaxarova
Классическими примерами, приводимыми во многих статьях по проблемам переноса программ на 64-битные системы, является некорректное использование функций printf, scanf и их разновидностей.
В некачественном коде часто встречаются магические числовые константы, наличие которых опасно само по себе. При миграции кода на 64-битную платформу эти константы могут сделать код неработоспособным, если участвуют в операциях вычисления адреса, размера объектов или в битовых операциях.
Урок 8. Статический анализ для выявления 64-битных ошибокTatyanazaxarova
Статический анализ кода - методология выявления ошибок в программном коде, основанная на просмотре программистом участков кода, помеченных статическим анализатором. Помеченные участки кода с большой вероятностью содержат ошибки определенного типа.
Исправление всех ошибок компиляции и предупреждений не будет означать работоспособность 64-битного приложения. И именно описанию и диагностике 64-битных ошибок будет посвящена основная часть уроков. Также не надейтесь на помощь ключа /Wp64, который многими часто без оснований преподносится при обсуждениях в форумах как чудесное средство поиска 64-битных ошибок.
Хочется сразу предупредить читателя, что невозможно всесторонне описать процесс сборки 64-битного приложения. Настройки любого проекта достаточно уникальны, поэтому к адаптации настроек для 64-битной системы всегда надо подходить внимательно. В уроке будут описаны только общие шаги, которые важны для любого проекта. Эти шаги подскажут вам с чего начать процесс.
Вначале следует убедиться, что используемая вами редакция Visual Studio позволяет собирать 64-битный код. Если вы планируете разрабатывать 64-битные приложения с использованием последней версии (на момент написания курса) Visual Studio 2008, то следующая таблица поможет определить, какая из редакций Visual Studio вам необходима.
PVS-Studio - статический анализатор, выявляющий ошибки в исходном коде приложений на языке C/C++/C++0x. Можно выделить 3 набора правил, включенных в состав PVS-Studio:
1. Диагностика 64-битных ошибок (Viva64)
2. Диагностика параллельных ошибок (VivaMP)
3. Диагностика общего назначения
PVS-Studio научился следить за тем, как вы программируетеTatyanazaxarova
В PVS-Studio появился режим работы, который поможет максимально рано выявлять ошибки и опечатки. Анализатор запускается сразу после компиляции файлов и если что-то не так, покраснеет от стыда за ваш код. Фича доступна на данный момент только для пользователей Visual Studio 2010.
PVS-Studio научился следить за тем, как вы программируете
Проблемы 64-битного кода на примерах
1. Проблемы 64-битного кода на
примерах
Автор: Евгений Рыжков
Дата: 23.07.2007
Аннотация
Статья представляет собой рассмотрение примеров реальных проблем в Си++ коде,
проявляющихся при разработке 64-битных решений.
Данная статья содержит различные примеры 64-битных ошибок. Однако с момента ее написания,
мы узнали значительно больше примеров и типов ошибок, которые не описаны в этой статье. Мы
предлагаем вам познакомиться со статьей "Коллекция примеров 64-битных ошибок в реальных
программах", в которой наиболее полно описаны известные нам дефекты в 64-битных
программах. Также рекомендуем изучить "Уроки разработки 64-битных приложений на языке
Си/Си++", где описана методика создания корректного 64-битного кода и методы поиска всех
видов дефектов с использованием анализатора кода Viva64.
Введение
При переносе 32-битного программного обеспечения на 64-битные системы в коде приложений,
написанных на языке Си++, могут проявляться отсутствующие ранее ошибки. Причина этого
кроется в изменении базовых типов данных (а точнее, отношений между ними) на новой
аппаратной платформе. "Но ведь Си++ - высокоуровневый язык!", - воскликнете Вы и будете,
конечно же, правы. Однако все высокоуровневые абстракции этого языка реализуются через все
те же низкоуровневые типы данных.
Справочная документация для программистов, конечно же, содержит описание этих ошибок.
Однако даже такие авторитетные источники информации, как MSDN, зачастую пытаются
ограничиться лишь общими фразами, например:
• Типы int и long остались 32-битными на 64-битных версиях Windows;
• Типы size_t, time_t, и ptrdiff_t стали 64-битными на 64-битных версиях Windows.
Однако, что это означает для программиста, и в какие потенциальные проблемы может вылиться,
зачастую не сообщается.
Между тем статей, содержащих конкретные примеры ошибок в коде приложений для 64-битных
версий Windows, совсем мало. Настоящая статья и призвана восполнить этот пробел.
Немного терминологии. Под memsize-типом будем понимать любой тип данных, который меняет
свой размер при изменении разрядности архитектуры с 32 бит на 64 бита. Примеры: size_t,
ptrdiff_t, DWORD_PTR, LONG_PTR и другие.
Сразу же оговоримся, что в данной статье лишь кратко приводятся примеры ошибок. Объяснение
их причин можно найти в статье "20 ловушек переноса Си++ кода на 64-битную платформу"
http://www.viva64.com/art-1-2-599168895.html
2. Исходный код примера с ошибками
Не будем томить программистов, желающих поскорее приступить к изучению примеров ошибок,
и приведем полный исходный код такой программы. После исходного кода, каждая ошибка будет
рассмотрена отдельно.
Для демонстрации ошибок необходимо скомпилировать и запустить данный код в 64-битном
режиме.
Вы можете найти исходный код приложения в дистрибутиве Viva64 под названием PortSample.
Для этого скачайте и установите Viva64 (www.viva64.com/download.php), а затем установите
PortSample из программной группы Viva64.
bool IsX64Platform() {
return sizeof(size_t) == 8;
}
template <typename A, typename B>
inline size_t SafeMul(A a, B b) {
return static_cast<size_t>(a) * static_cast<size_t>(b);
}
template <typename A, typename B, typename C>
inline size_t SafeMul(A a, B b, C c) {
return static_cast<size_t>(a) * static_cast<size_t>(b) *
static_cast<size_t>(c);
}
template <typename A, typename B, typename C, typename D>
inline size_t SafeMul(A a, B b, C c, D d) {
return static_cast<size_t>(a) * static_cast<size_t>(b) *
static_cast<size_t>(c) * static_cast<size_t>(d);
}
void V101()
{
unsigned imageWidth = 1000;
unsigned imageHeght = 1000;
unsigned bytePerPixel = 3;
unsigned maxFrameCountInBuffer;
10. for (size_t i = 0; i != 10; ++i)
sum += sizetPtr[i];
if (sum != 45)
throw CString(_T("x64 portability issues"));
}
void V301()
{
class CWinAppTest {
public:
virtual void WinHelp(DWORD_PTR, UINT) {
::AfxMessageBox(_T("Cannot activate WinHelp"));
}
};
class CPortSampleApp : public CWinAppTest {
public:
virtual void WinHelp(DWORD, UINT) {
::AfxMessageBox(_T("WinHelp activated"));
}
};
CWinAppTest *Application = new CPortSampleApp();
Application->WinHelp(NULL, 0);
delete Application;
}
int _tmain(int argc, TCHAR* argv[])
{
V101();
V102();
V103();
V104();
V105();
11. V106();
V107();
V108();
V109();
V110();
V111();
V112();
V113();
V114();
V201();
V202();
V203();
V301();
return 0;
}
Теперь, когда Вы увидели весь код, давайте рассмотрим все функции, содержащие ошибки. Когда
мы говорим, что функции содержат ошибки, мы имеем в виду следующее. Представленный код
прекрасно компилируется и работает в 32-битном режиме. Однако, после компиляции для 64-
битного режима, работа программы становится некорректной, вплоть до падений.
Неявное приведение к типу memsize
void V101()
{
unsigned imageWidth = 1000;
unsigned imageHeght = 1000;
unsigned bytePerPixel = 3;
unsigned maxFrameCountInBuffer;
if (IsX64Platform()) {
maxFrameCountInBuffer = 2000;
} else {
maxFrameCountInBuffer = 100;
}
12. size_t bufferSize = imageWidth * imageHeght *
bytePerPixel * maxFrameCountInBuffer;
BYTE *buffer = static_cast<BYTE *>(malloc(bufferSize));
BYTE *ptr = buffer;
for (unsigned frame = 0; frame != maxFrameCountInBuffer; ++frame)
for (unsigned width = 0; width != imageWidth; ++width)
for (unsigned height = 0; height != imageHeght; ++height) {
*ptr++ = 0xFF;
*ptr++ = 0xFF;
*ptr++ = 0x00;
}
free (buffer);
}
Проблема скрыта в следующей строке:
size_t bufferSize = imageWidth * imageHeght *
bytePerPixel * maxFrameCountInBuffer;
Все переменные, участвующие в умножении, имеют тип unsigned, который и в 32-х и 64-битном
режиме имеет размер 32 бита. Однако результат умножения записывается в переменную типа
size_t, который в 32-битном режиме имеет размер, совпадающий с размером типа unsigned, а в
64-битном - не совпадающий. Но компилятор прекрасно выполняет расширение результирующего
типа до unsigned. Казалось бы, проблемы нет? Есть! Если в результате умножения результат
превысит 4 гигабайта, то переполнение произойдет и результат будет неверным.
Использование не memsize-типа для арифметики с указателями
void V102()
{
int domainWidth;
int domainHeght;
int domainDepth;
if (IsX64Platform()) {
domainWidth = 2000;
domainHeght = 2000;
13. domainDepth = 2000;
} else {
domainWidth = 500;
domainHeght = 500;
domainDepth = 500;
}
char *buffer =
new char [size_t(domainWidth) * size_t(domainHeght) *
size_t(domainDepth)];
char *current = buffer;
char *end = buffer;
end += domainWidth * domainHeght * domainDepth;
while (current != end)
*current++ = 1;
delete [] buffer;
}
Проблема в данном коде заключается в арифметике с указателями, точнее в использовании для
этой арифметики не memsize-типов:
end += domainWidth * domainHeght * domainDepth;
Ошибка в том, что на 64-битной платформе указатель end никогда не получит приращение более 4
гигабайт.
Неявное приведение memsize-типа
void V103()
{
size_t Megabyte = 1048576;
size_t Gigabyte = 1073741824;
size_t n = IsX64Platform() ? Gigabyte : Megabyte;
unsigned arraySize = n * sizeof(INT_PTR);
INT_PTR *buffer = (INT_PTR *)malloc(size_t(arraySize));
14. for (size_t i = 0; i != n; ++i)
buffer[i] = 0;
free(buffer);
}
В следующем фрагменте кода присутствует совершенно очевидная ошибка:
unsigned arraySize = n * sizeof(INT_PTR);
Это неявное приведение к unsigned-типу переменной большей разрядности (на 64-битной
платформе).
Неявное приведение к memsize-типу в арифметическом выражении
void V104()
{
volatile size_t n;
if (IsX64Platform()) {
n = SafeMul(5, 1024, 1024, 1024);
} else {
n = SafeMul(5, 1024, 1024);
}
char *buffer = new char [n];
volatile size_t index = 0;
volatile unsigned i;
for (i = 0; i != n; ++i)
buffer[index++] = 1;
delete [] buffer;
}
Операции вроде сравнения двух переменных, как ни странно, также могут стать источником
проблем.
В следующей строке:
for (i = 0; i != n; ++i)
15. проблема в том, что переменная i типа unsigned сравнивается с переменной n типа size_t, после
чего происходит ее увеличение. Однако из-за того, что unsigned никогда не превышает 4 гигабайт,
то переменная i никогда не будет больше этого значения. Что мы имеем в результате?
Бесконечный цикл, так как условие i != n будет выполняться всегда.
Неявное приведение к memsize типу в операции ?:
void V105()
{
bool flag = true;
unsigned a = unsigned(-1);
if ((flag ? a : sizeof(float)) != size_t(-1)) {
throw CString("x64 portability issues");
}
}
Этот пример очень похож на предыдущий. Проблема в следующей строке:
if ((flag ? a : sizeof(float)) != size_t(-1)) {
Здесь переменная a имеет тип unsigned, который при сравнении с size_t может дать некорректный
результат. Почему? Да потому, что unsigned(-1) - это не тоже самое, что size_t (-1). Неявное
приведение аргумента функции к memsize-типу.
void V106()
{
void *buffer;
const unsigned Megabyte = 1024 * 1024;
const unsigned Gigabyte = 1024 * 1024 * 1024;
unsigned unit;
if (IsX64Platform())
unit = Gigabyte;
else
unit = Megabyte;
buffer = malloc(5 * unit);
if (IsX64Platform())
memset(buffer, 0, SafeMul(5, 1024, 1024, 1024));
16. else
memset(buffer, 0, SafeMul(5, 1024, 1024));
free(buffer);
}
В строке:
buffer = malloc(5 * unit);
программист рассчитывал получить на 64-битной системе буфер из 5 гигабайт. Однако здесь
произойдет ошибка. Почему? Функция malloc() принимает аргумент memsize-типа и 5 гигабайт -
вполне подходящее число. Однако при умножении (5 * unit) произойдет переполнение, т.к.
переменная unit имеет тип unsigned. В результате получится вовсе не 5 гигабайт.
Неявное приведение аргумента функции memsize-типа к 32-
битному типу
void V107_FillFunction(char *array, unsigned arraySize) {
for (unsigned i = 0; i != arraySize; ++i)
array[i] = 1;
}
void V107()
{
size_t n;
if (IsX64Platform()) {
n = SafeMul(5, 1024, 1024, 1024);
} else {
n = SafeMul(5, 1024, 1024);
}
char *array = (char *)malloc(n * sizeof(char));
memset(array, 0, n * sizeof(char));
V107_FillFunction(array, n);
for (size_t i = 0; i != n; ++i)
if (array[i] != 1)
throw CString("x64 portability issues");
free(array);
17. }
В строке, с вызовом функции
V107_FillFunction(array, n);
происходит приведение типа переменной n к unsigned. Это означает усечение значения
переменной, в результате чего может быть заполнен не весь массив.
Использование для индексации некорректных типов
void V108()
{
size_t n;
if (IsX64Platform()) {
n = SafeMul(5, 1024, 1024, 1024);
} else {
n = SafeMul(5, 1024, 1024);
}
char *array = (char *)malloc(n * sizeof(char));
memset(array, 0, n * sizeof(char));
volatile int index = 0;
for (size_t i = 0; i != n; ++i) {
array[index++] = 1;
if (array[i] != 1)
throw CString("x64 portability issues");
}
free(array);
}
Если для индексации массива использовать не memsize-тип, то может произойти ошибка вида:
array[index++] = 1;
Проблема состоит в том, что в случае если в массиве присутствует более 4 гигабайт элементов, то
обратится к ним с помощью переменной типа unsigned будет невозможно.
18. Приведение к memsize-типу при использовании возвращаемого
значения
ptrdiff_t UnsafeCalcIndex(int x, int y, int width) {
volatile int result = x + y * width;
return result;
}
void V109()
{
int domainWidth;
int domainHeght;
if (IsX64Platform()) {
domainWidth = 50000;
domainHeght = 50000;
} else {
domainWidth = 5000;
domainHeght = 5000;
}
char *array = (char *)malloc(SafeMul(domainWidth, domainHeght));
for (int x = 0; x != domainWidth; ++x)
for (int y = 0; y != domainHeght; ++y) {
array[UnsafeCalcIndex(x, y, domainWidth)] = 55;
}
free(array);
}
Удивительно, но в данном примере ошибка содержится в строке:
return result;
Переменная result имеет тип int, который неявно будет расширен до ptrdiff_t. Однако функция
UnsafeCalcIndex() никогда не сможет вернуть индекс элемента, следующего за 2 гигабайтами.
Конечно, правильнее сказать, что ошибка в неудачно выбранном типе переменной result. Эта
переменная в данном случае должна иметь тип ptrdiff_t.
19. Приведение memsize-типа при использовании возвращаемого
значения
int UnsafeStrLen(const char *text) {
const char *ptr = text;
while (*ptr != 0)
++ptr;
return ptr - text;
}
void V110()
{
size_t n;
CString trueSize;
if (IsX64Platform()) {
n = SafeMul(3, 1024, 1024, 1024);
trueSize = _T("3221225472");
} else {
n = SafeMul(3, 1024, 1024);
trueSize = _T("3145728");
}
char *str = (char *)malloc(n * sizeof(char));
memset(str, 'V', n * sizeof(char));
str[n - 1] = 0;
int len = UnsafeStrLen(str);
CString falseSize;
falseSize.Format(_T("%i"), len + 1);
if (falseSize != trueSize)
throw CString(_T("x64 portability issues"));
}
Ситуация повторяет предыдущий пример, ошибка опять в строке возврата значения:
return ptr - text;
20. Отличие лишь в том, что здесь выполняется приведение memsize типа к типу int. В результате
размер буфера (из примера) никогда не сможет быть вычислен в случае, если он больше 2
гигабайт.
Вызов функции с переменным числом аргументов с memsize-
параметром
void V111()
{
char invalidStr[100], validStr[100];
const char *invalidFormat = "%u";
const char *validFormat = "%Iu";
size_t a = SIZE_MAX;
sprintf_s(invalidStr, sizeof(invalidStr),invalidFormat, a);
sprintf_s(validStr, sizeof(validStr), validFormat, a);
if (strcmp(invalidStr, validStr) != 0)
throw CString(_T("x64 portability issues"));
}
Функции с переменным числом аргументов очень часто используются для форматирования и
ввода/вывода текстовых строк. Некорректное задание строки формата может привести к
неправильной работе:
const char *invalidFormat = "%u";
sprintf_s(invalidStr, sizeof(invalidStr),invalidFormat, a);
Строка формата в данном примере рассчитана на 32-битный режим работы и в 64-битном режиме
приведет к неправильному выводу.
Неявное приведение memsize-типа к double и наоборот
void V113()
{
size_t a = size_t(-1);
double b = a;
--a;
--b;
size_t c = b;
21. if (a != c)
throw CString(_T("x64 portability issues"));
}
В представленном примере ошибки содержатся в строках:
double b = a;
и
size_t c = b;
Такое присваивание на 64-битных системах некорректно, так как может вызвать потерю точности.
Явные приведения типов при работе с указателями
void V114()
{
unsigned intPtr[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
size_t *sizetPtr = (size_t *)(intPtr);
size_t sum = 0;
for (size_t i = 0; i != 10; ++i)
sum += sizetPtr[i];
if (sum != 45)
throw CString(_T("x64 portability issues"));
}
Язык Си++, являясь низкоуровневым языком, позволяет работать с памятью на уровне указателей.
Явные приведения типов при использовании указателей являются опасными в любом случае,
однако приведение memsize типов как показано в примере, вдвойне опаснее:
size_t *sizetPtr = (size_t *)(intPtr);
Все дело в разности размеров типов size_t и unsigned.
Перекрытие виртуальных функций
void V301()
{
class CWinAppTest {
public:
virtual void WinHelp(DWORD_PTR, UINT) {
22. ::AfxMessageBox(_T("Cannot activate WinHelp"));
}
};
class CPortSampleApp : public CWinAppTest {
public:
virtual void WinHelp(DWORD, UINT) {
::AfxMessageBox(_T("WinHelp activated"));
}
};
CWinAppTest *Application = new CPortSampleApp();
Application->WinHelp(NULL, 0);
delete Application;
}
Одна из самых забавных ошибок в Си++ приложениях, которая может проявиться на 64-битных
системах, связана с виртуальными функциями. Обратите внимание на параметры виртуальных
функций в примере выше. На 32-битной системе DWORD_PTR и DWORD совпадают, и получается
перекрытая виртуальная функция, а на 64-битной - это две разных функции! В результате вызов
функции WinHelp() из примера приведет к появлению сообщения "Cannot activate WinHelp".
Вместо заключения
Итак, мы перечислили основные ошибки в коде, которые проявляются при переносе приложений
на 64-битные системы? Вы скажете, что многие из них надуманы? Кому, например, может
понадобиться буфер в 5 гигабайт на Windows-системе? Может быть, в 2007 году, это еще и не
столь актуально, хотя многие ресурсоемкие приложения уже могут использовать такой объем
памяти. Однако посмотрим, будет ли востребована эта статья уже через пару лет. Кто знает,
может быть именно Вы будете долго отлаживать ошибку, возникающую при выделении
нескольких гигабайт памяти.
Информация об авторе
Евгений Рыжков, один из создателей статического анализатора кода Viva64 (www.Viva64.com),
предназначенного для упрощения переноса приложений на 64-битные платформы. Исследует
проблемы миграции 32-битных программных систем на 64-битные платформы.