Читаем 19 смертных грехов, угрожающих безопасности программ полностью

Если даже не обращать внимания на усечение, то вот вопрос: в каком порядке производятся приведения типов при сравнении len и MAX_LEN? Стандарт языка гласит, что повышающее приведение следует выполнять перед сравнением; следовательно, len будет преобразовано из 16–разрядного целого со знаком в 32–разрядное целое со знаком. Это простое приведение, так как оба типа знаковые. Чтобы сохранить значение числа, оно расширяется с сохранением знака до более широкого типа. В данном случае мог бы получиться такой результат:

...

len = 0x100;

(long)len = 0x00000100;

ИЛИ

...

len = 0xffff;

(long)len = 0xfffffffff;

Поэтому если противник сумеет добиться того, чтобы len превысило 32К, то len станет отрицательным и останется таковым после расширения до 32 битов. Следовательно, после сравнения с MAX_LEN программа пойдет по неверному пути.

Вот как формулируются правила преобразования в С и С++:

Целое со знаком в более широкое целое со знаком. Меньшее значение расширяется со знаком, например приведение (char)0x7f к int дает 0x0000007f, но (char)0x80 становится равно 0xffffff80.

Целое со знаком в целое без знака того же размера. Комбинация битов сохраняется, значение может измениться или остаться неизменным. Так, (char)0xff (-1) после приведения к типу unsigned char становится равно 0xff, но ясно, что–1 и 255 – это не одно и то же.

Целое со знаком в более широкое целое без знака. Здесь сочетаются два предыдущих правила. Сначала производится расширение со знаком до знакового типа нужного размера, а затем приведение с сохранением комбинации битов. Это означает, что положительные числа ведут себя ожидаемым образом, а отрицательные могут дать неожиданный результат. Например, (char) -1 (0xff) после приведения к типу unsigned long становится равно 4 294 967 295 (0xffffffff).

Целое без знака в более широкое целое без знака. Это простейший случай: новое число дополняется нулями, чего вы обычно и ожидаете. Следовательно, (unsigned char)0xff после приведения к типу unsigned long становится равно

0x000000ff.

Целое без знака в целое со знаком того же размера. Так же как при приведении целого со знаком к целому без знака, комбинация битов сохраняется, а значение может измениться в зависимости от того, был ли старший (знаковый) бит равен 1 или 0.

Целое без знака в более широкое целое со знаком. Так же как при приведении целого без знака к более широкому целому без знака, значение сначала дополняется нулями до нужного беззнакового типа, а затем приводится к знаковому типу. Значение не изменяется, так что никаких сюрпризов в этом случае не бывает.

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

Преобразования при вызове операторов

Большинство программистов не подозревают, что одного лишь обращения к оператору достаточно для изменения типа результата. Обычно ничего страшного не происходит, но граничные случаи могут вас неприятно удивить. Вот код на С++, иллюстрирующий проблему:

...

template

void WhatIsIt(T value)

{

if((T)-1 < 0)

printf("Со знаком");

else

printf("Без знака");

printf(" – %d бит\n", sizeof(T)*8);

}

Для простоты оставим в стороне случай смешанных операций над целыми и числами с плавающей точкой. Правила формулируются так:

□ если хотя бы один операнд имеет тип unsigned long, то оба операнда приводятся к типу unsigned long. Строго говоря, long и int – это два разных типа, но на современных машинах тот и другой имеют длину 32 бита, поэтому компилятор считает их эквивалентными;

□ во всех остальных случаях, когда длина операнда составляет 32 бита или меньше, операнды расширяются до типа int, и результатом является значение типа int.

Перейти на страницу:

Похожие книги

Основы программирования в Linux
Основы программирования в Linux

В четвертом издании популярного руководства даны основы программирования в операционной системе Linux. Рассмотрены: использование библиотек C/C++ и стан­дартных средств разработки, организация системных вызовов, файловый ввод/вывод, взаимодействие процессов, программирование средствами командной оболочки, создание графических пользовательских интерфейсов с помощью инструментальных средств GTK+ или Qt, применение сокетов и др. Описана компиляция программ, их компоновка c библиотеками и работа с терминальным вводом/выводом. Даны приемы написания приложений в средах GNOME® и KDE®, хранения данных с использованием СУБД MySQL® и отладки программ. Книга хорошо структурирована, что делает обучение легким и быстрым. Для начинающих Linux-программистов

Нейл Мэтью , Ричард Стоунс , Татьяна Коротяева

ОС и Сети / Программирование / Книги по IT
97 этюдов для архитекторов программных систем
97 этюдов для архитекторов программных систем

Успешная карьера архитектора программного обеспечения требует хорошего владения как технической, так и деловой сторонами вопросов, связанных с проектированием архитектуры. В этой необычной книге ведущие архитекторы ПО со всего света обсуждают важные принципы разработки, выходящие далеко за пределы чисто технических вопросов.?Архитектор ПО выполняет роль посредника между командой разработчиков и бизнес-руководством компании, поэтому чтобы добиться успеха в этой профессии, необходимо не только овладеть различными технологиями, но и обеспечить работу над проектом в соответствии с бизнес-целями. В книге более 50 архитекторов рассказывают о том, что считают самым важным в своей работе, дают советы, как организовать общение с другими участниками проекта, как снизить сложность архитектуры, как оказывать поддержку разработчикам. Они щедро делятся множеством полезных идей и приемов, которые вынесли из своего многолетнего опыта. Авторы надеются, что книга станет источником вдохновения и руководством к действию для многих профессиональных программистов.

Билл де Ора , Майкл Хайгард , Нил Форд

Программирование, программы, базы данных / Базы данных / Программирование / Книги по IT
Программист-прагматик. Путь от подмастерья к мастеру
Программист-прагматик. Путь от подмастерья к мастеру

Находясь на переднем крае программирования, книга "Программист-прагматик. Путь от подмастерья к мастеру" абстрагируется от всевозрастающей специализации и технических тонкостей разработки программ на современном уровне, чтобы исследовать суть процесса – требования к работоспособной и поддерживаемой программе, приводящей пользователей в восторг. Книга охватывает различные темы – от личной ответственности и карьерного роста до архитектурных методик, придающих программам гибкость и простоту в адаптации и повторном использовании.Прочитав эту книгу, вы научитесь:Бороться с недостатками программного обеспечения;Избегать ловушек, связанных с дублированием знания;Создавать гибкие, динамичные и адаптируемые программы;Избегать программирования в расчете на совпадение;Защищать вашу программу при помощи контрактов, утверждений и исключений;Собирать реальные требования;Осуществлять безжалостное и эффективное тестирование;Приводить в восторг ваших пользователей;Формировать команды из программистов-прагматиков и с помощью автоматизации делать ваши разработки более точными.

А. Алексашин , Дэвид Томас , Эндрю Хант

Программирование / Книги по IT