Программирование >>  Арифметические и логические операции 

1 ... 21 22 23 [ 24 ] 25 26 27 ... 53


Однако вспомним, что менеджер памяти у нас свой в доску , и мы можем обойтись действительно переносимой конструкцией. Тело анализа достаточно разместить сразу за самим объектом, статический размер которого нам всегда известен. Ещё чуть-чуть правим:

class Blood: public BCAR {

friend BCAR;

private: int bodySize;

int size() { return sizeof( Blood ) + bodySize; } int getSize( const unsigned char * ); struct BloodAnalysisBody { тут его поля

} *body;

Blood( const unsigned char *data ): BCAR() { body = (BloodAnalysisBody *) ((unsigned char *)this

+ sizeof( Blood )); bodySize = getSize( data );

::memcpy( body, data + sizeof( header ), bodySize ); MemoryManager.alloc( size() );

Данные гарантированно ложатся сразу за объектом в памяти, которую нам дал MemoryManager (а он, напомним, даёт нам всегда максимум из того, что имеет), а затем alloc соответствующим образом всё подправит.

Глава 11.

Чтение исходных текстов

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

Существует программирование на каком-то языке программирования. Программист, претендующий на то, что он является программис-

том на Бейсике , на Паскале , на Си или на C++ может вообще ничего не знать о существовании конкретных операционных систем и архитектур, но обязан при этом знать тонкости своего языка программирования, знать о том, какие конструкции языка для чего эффективнее и т.д. и т.п.

Понятно, что программирование на языке и программирование под архитектуру могут вполне пересекаться. Программист на С под Unix, например. Тем не менее, в особенности среди новичков, часто встречается подмена этих понятий. Стандартный вопрос новичка: я только начал изучать C++, подскажите пожалуйста, как создать окно произвольной формы? . В общем, нельзя, наверное, изучать сразу же операционную систему и язык программирования. Вопрос же про окно правомерен, наверное, только для Java, где работа с окнами находится в стандартной библиотеке языка. Но у C++ своя специфика, потому что даже задав вопрос: Как напечатать на экране строку Hello, world!? можно напороться на раздраженное Покажи где у C++ экран? .

Тем не менее, возвратимся к исходным текстам. Начинающие программисты обычно любят устраивать у себя разнообразные коллекции исходных текстов. Т.е., с десяток эмуляторов терминалов, пятнадцать библиотек пользовательского интерфейса, полсотни DirectX и OpenGL программ на Си . Кстати сказать, программирование с использованием OpenGL тоже можно отнести к отдельному классу, который ортогонален классам операционная система и язык программирования . Почему-то люди упорно считают, что набрав большое количество различных программ малой, средней и большой тяжести, они решат себе много проблем, связанных с программированием. Это не так - совершенно не понятно, чем на стадии обучения программированию может помочь исходник Quake.

Этому есть простое объяснение. Читать исходные тексты чужих программ (да и своих, в принципе, тоже) очень нелегко. Умение читать программы само по себе признак высокого мастерства. Смотреть же на текст программ как на примеры использования чего-либо тоже нельзя, потому что в реальных программах есть очень много конкретных деталей, связанных со спецификой проекта, авторским подходом к решению некоторых задач и просто стилем программирования, а это очень сильно загораживает действительно существенные идеи.

Кроме того, при чтении исходников , очень часто программирование на языке незаметно заменяется программированием под ОС . Ведь всегда хочется сделать что-то красивое, чем можно удивить родителей или знакомых? А еще хочется сделать так, чтобы как в Explorer - трудно забыть тот бум на компоненты flat buttons для Delphi/C++ Builder,



когда только появился Internet Explorer 3.0. Это было что-то страшное, таких компонент появилось просто дикое количество, а сколько программ сразу же появилось с их присутствием в интерфейсе...

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

Чтение исходных текстов полезно, но уже потом, когда проблем с языком не будет (а до этого момента можно только перенять чужие ошибки), их коллекционирование никак не может помочь в начальном изучении языка программирования. Приемы, которые применяются разработчиками, значительно лучше воспринимаются когда они расписаны без излишних деталей и с большим количеством комментариев, для этого можно посоветовать книгу Джеффа Элджера C++ .

Глава 12. Функция gets()

Функция gets(), входящая в состав стандартной библиотеки Си, имеет следующий прототип:

char* gets(char* s);

Это определение содержится в stdio.h. Функция предназначена для ввода строки символов из файла stdin. Она возвращает s если чтение прошло успешно и NULL в обратном случае.

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

Вообще говоря, для тех, кто не знает, почему использование функции gets() так опасно, будет полезно посмотреть еще раз на ее прототип, и подумать. Все дело в том, что для gets() нельзя, т.е. совершенно невозможно, задать ограничение на размер читаемой строки, во всяком случае, в пределах стандартной библиотеки. Это крайне опасно, потому что тогда при работе с вашей программой могут возникать различные сбои при обычном вводе строк пользователями. Т.е., например:

char name[10];

...

puts( Enter you name: ); gets(name);

Если у пользователя будет имя больше, чем 9 символов, например, 10, то по адресу (name + 10) будет записан 0. Что там на самом деле находится, другие данные или просто незанятое место (возникшее, например, из-за того, что компилятор соответствующим образом выровнял данные), или этот адрес для программы недоступен, неизвестно.

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

Опять же, для программиста самым удобным будет моментальное аварийное прекращение работы программы в этом месте - тогда он сможет заменить gets() на что-нибудь более порядочное .

У кого-то может возникнуть предложение просто взять и увеличить размер буфера. Но не надо забывать, что всегда можно ввести строку длиной, превышающий выделенный размер; если кто-то хочет возразить, что случаи имен длиной более чем, например, 1024 байта все еще редки, то перейдем к другому, несколько более интересному примеру возникающей проблемы при использовании gets().

Для это просто подчеркнем контекст, в котором происходит чтение строки.

char name[10];

puts( Enter you name: ); gets(name);

...



Имеется в виду, что теперь name расположен в стеке. Надеемся, что читающие эти строки люди имеют представление о том, как обычно выполняется вызов функции. Грубо говоря, сначала в стек помещается адрес возврата, а потом в нем же размещается память под массив name. Так что теперь, когда функция gets() будет писать за пределами массива, она будет портить адрес возврата из функции foo().

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

Немного отвлечемся, потому что это достаточно интересно. В операционной системе Unix есть возможность запускать программы, которые будут иметь привилегии пользователя, отличного от того, кто этот запуск произвел. Самый распространенный пример, это, конечно же, суперпользователь. Например, команды ps или passwd при запуске любым пользователем получают полномочия rootа. Сделано это потому, что копаться в чужой памяти (для ps) может только суперпользователь, так же как и вносить изменения в /etc/passwd. Понятно, что такие программы тщательнейшим образом проверяются на отсутствие ошибок - через них могут утечь полномочия к нехорошим хакерам (существуют и хорошие!). Размещение буфера в стеке некоторой функции, чей код выполняется с привилегиями другого пользователя, позволяет при переполнении этого буфера изменить на что-то осмысленное адрес возврата из функции. Как поместить по переданному адресу то, что требуется выполнить (например, запуск командного интерпретатора), это уже другой разговор и он не имеет прямого отношения к программированию на С или C++.

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

fgets(name, 10, stdin);

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

Глава 13. Свойства

Когда только появилась Delphi (первой версии) и ее мало кто видел, а еще меньше людей пробовали использовать, то в основном сведения об этом продукте фирмы Borland были похожи на сплетни. Помнится такое высказывание: Delphi расширяет концепции объектно-ориентированного программирования за счет использования свойств .

Те, кто работал с C++ Builder, представляет себе, что такое его свойства. Кроме того, кто хоть как-то знаком с объектно-ориентированным анализом и проектированием, понимает - наличие в языке той или иной конструкции никак не влияет на используемые подходы. Мало того, префикс ОО обозначает не использование ключевых слов class или property в программах, а именно подход к решению задачи в целом (в смысле ее архитектурного решения). С этой точки зрения, будут использоваться свойства или методы set и get, совершенно без разницы - главное разграничить доступ.

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

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

a = b;

нельзя сразу же сказать, что произойдет. Потому что на оператор присваивания можно повесить все что угодно. Самый распространенный пример неправильного (в смысле, изменение исходных целей) использования операций являются потоки ввода-вывода, в которых операторы побитового сдвига выполняют роль printf. Примерно то же нарекание можно отнести и к использованию свойств.

Тем не менее, перейдем к описанию примера. Итак, нужно оформить такой объект, при помощи которого можно было бы делать примерно следующее:

class Test



1 ... 21 22 23 [ 24 ] 25 26 27 ... 53

© 2006 - 2024 pmbk.ru. Генерация страницы: 0.001
При копировании материалов приветствуются ссылки.
Яндекс.Метрика