Г. Шилдт - Полный справочник по C++ (1109478), страница 26
Текст из файла (страница 26)
1п"]; ьт(!(*егер)(а, Ы ) рхапет("Равны" ); е1ве рггпет("Не равны"); ) ьпе ппесгар(сопве сиат *а, сопве снах *Ъ) г б(аеоь(а) ==асов(Ы ) теентп Ор е1ве хеенхп 1; ) Если пользователь введет строку, состоящую из букв, функция сьев)т() вызовет функцию вегевр(), получив указатель на нее. В противном случае будет передан указатель на функцию певнввр(). Итак, в разных ситуациях функция сиес)е() может вызывать разные функции. ~( Функции динамического расспределения памяти указатели позволяют динамически распределять память в программах на языке С/С++ Термин динамическое раснределение ламлти (йупапцс а1)осайоп) означает, что программа может получать необходимую ей память уже в ходе своего выполнения, Глава 5.
указатели Как известно, память для глобальных псрсчсш)ых выделяется па этапе компиляции Локальпыс персмспиыс хранятся в стеке. Слсдовагсльпо, в ходе выполнения про. граммы пснозможпо объявить поные глобальпыс или локал1ш)яс псремсппыс. И все же иногда в ходе выполпспия программы возникает необходимость вылепить допол иитсльпую память, разчср которои заранее пс извес)си. Например, программа можс~ использовать динамические структуры — снязаппыс списки или лсревья.
Такие струк зуры данных являются динамическими по сноси природе. Опп увеличиваются или умспьша|отся по морс необходимости. Д;т го~о побы реализовать такис структуры даипыл, программа должна иметь возможность распределять и освобождать память. В языке С+-) предусмотрены лне системы для липамического распрслслспия памяти: олпа унаследована от языка С, вторая присупга лишь языку Сч+. Вторая система более совершенна.
Ес мы рассмотрич во второи асти книги, а пока сосрслоточимся па системс динамического распределения памяти, прсдусмотрсшюи языком С. Память, нылслясмая функциями дипамкчсского распределения, находится н кук ()зсар), которая прслстанляст собои область свобоаиои памяти, расположенную между колом программы, ссгмсшом даипых и стеком. Хогя разчср ку ш зарапсс пс изнсстсп. ес объем обычно достаточно велик. В основе системы динамического распределения памяти н языке С лежат Функции ва11ое () и акен () х. (Во многих компиляторах предусмотрены дополпптсльпыс функции, позволяюшис дипал1ически выделять память, однако з.и две — самые наж. пые.) Эти функции создают и поддерживают список с|юбодпой памяти.
Функция ва11ос() выделяет память для объектов, а функция ееее(> освобождает сс. Иными словами, при каждом вызове функция ва11ос() вылсляст дополнительный участок памяти, а функция ееееО нознра~цает его опсрациошюп системс. Любая про~дамма, используюшая зти Функции, должна включать в себя заголовочпыи фаил аед1хь.ь. (В программах ца языке Се+ можно также использовать поныи стиль заголово шого фаила <саех(11ь>.) Прототип функции ва11ос О имеет следуюшии вид.
во1П *ва11ос(а1ае е каличсгнто байтов) Параметр колитство баииюн залает размер памяти, которую необходимо выделить (Тип адан е определен в заголовочном фаилс неп11ь.ь как целое число без знака.) Функция ва11ос() нозврашаст указатель типа згохд . Это означает, что его можно присваивать указазелю любого типа. В случае успеха функция ва11оо ( ) возврашаст указатель иа первый байт памяти. вылелеппои н куче. Если размера к) ~и недостаточно для успешного вьшслсиия памяти, функция ва11ое () возврашает пулевой указатель.
В приведенном цижс фрагменте программы выделяется 1000 баит непрерывной памяти. 1 =*' спас *р; Р = ва11ос(1000): Г* псе 1000 ЬУСен *г После выполнения оператора присваивания указатель р ссылается иа псрвыи из 1000 байт выделенной памяти. Обратите внимание иа то, что н прсльшушсм примере указатель, возврашаемыи функцией ва11ос(). приснаиваезся указателю р без приведе)пш типа. В языке С это допускается, поскольку указатель типа чодд* антомгпичсски преобразовывастся н тип указателя, стоящего н левой части оператора присваивания. Однако следует иметь в ниду, что в языке С++ зто правило не действует.
Следовательно, в программах па языке С++ при приснаива))ии указателя типа чодп" указателю другого типа необходимо выполнять явное приведение: $ р = (снах *) ва)1ос(1000) Час)ь ). Основы языка Сн; подмножество С В принципе, это правило распространяется на любое приснаиванис или преобразование указателей. В этом заключается одно из принципиальных различии лгежду языками С и С++. В следующем примере выделяется память для 50 целых чисел. Обратитс внимание на то, что применение оператора в1веод гарантирует машинонсзависимость этого фрагмента программы.
1пс *р; р:= (1пг *) юа11ос(50*вьтеся(гпг)); Поскольку размср кучи ограничен, при выделении памяти необходимо проверять указатсль, возврашасмый функцисй еа11ос(). Если он равен нулю, продолжать ныполнснис программы опасно. Проиллюстрирусм эту мысль слсдукндим примером. р = (йпг *) юа11ос(100); ьт((р) рхьпСГ("Память исчерпана.М"); ехйг (1); ) Разумеется, соли указатель р является нулевым, нс обязательно выпрыгивать на ходу — вызов функции еж1с() можно заменить какой-нибудь обработкой возникшей ошибки и продолжить ныполнснис программы.
Достаточно убелиться, что указатель р больше не равен нулю. Функция дкее() является а1гтиподом функции те11ос() — она оснобождаст раисе занятую область динамичсской памяти. Освобожданную память можно снова использовать с помощью повторного нызова функции ее11осО . Про~огни функции дкее() выглядит слсдуюшим образом. чо1е дкее(чо1гт лр) Здесь парамстр р янлястся указатслсм на участок памяти, ранее выдслснный функцией ее11ос(>, Принципиально важно то, что функцию дкее() ни в косм случае нельзя нызынать с нспранильным аргументом — это разрушит список свободной памяти. =-''4 Проблемы, возникающие при работе с указателями Указатели — головная боль программистов! С одной стороны, они прсдставляют собой чрезвычайно мощный и нсобходимый во многих ситуациях механизм.
С другой стороны, ссли указатель содержит нспрсднидсннос значение, обнаружить эту ошибку крайне трудно. Неверный указатель трудно найти, поскольку сам по себе он ошибкой нс является. Проблсмы возникают тогда, когда, пользуясь ошибочным указателем, вы начинастс считывать или записывать информацию н нсизвсспгой области палгяти. Если вы считынастс сс, в худшсм случае получитс мусор, но если записынастс — берсгитссь! Вы можете повредить данные, код программы и дажс операционную систему. Сначала ошибка может никак не проявиться, олнако в ходе выполнения программы это может принести к нспрсдсказусмым последствиям. Такая ошибка напоминает мину замсд1снного действия, причем совершенно непонятно, где сс слсдует исказь. Поистине, ошибочный указатсль — это ночной кошмар программиста. Программа может даже успешно завершиться, но операционная сисгема после ее выполнения может оказаться поврежденной.
Таким образом, мало, чтобы программа делала то, что от иеа требуют, нужно, чтобы она была безопасной лля своего окружения. — Прим. Лед. Глава 5. Указатели Чтобы ваш сои был глубок и спокоен, при рабозе с указателями следует соблюдать особую осторожность. Рассмотрим несколько рецептов, позволяющих избежать ненужных проблем. Классический пример ошибки при работе с указателями — ле(ьчициолизидовпнвый игазаглель. Рассмотрим следующую программу. /* Осторожно: мины! */ 1пс ма1п(чо1Ю 1пе х, *р; х = 10 *р = х тесцеп О; В этой програмл1с число 10 записывается в некую неизвестную область памяти. Причина заключается в том, что в момент присваивания *р х указатель р содержит пеопредслепнгяй адрес.
Следовательно, невозможно предсказать, куда будет записано значение переменной х. Если программа невелика, такие ошибки могут себя никак ис проявлять, поскольку вероятность того, что указатель р содержит "безопасный"' адрес, достаточно велика, и ваши данные, программа и операционная система могут избежать опасности. Однако, сели программа имеет большой размер, всроят((ость повредить жизненно важныс области памяти резко возрастает. В конце концов программа перестанет работать.
Для того чтобы избежать таких ситуаций, указатели всегда следует "заземлять", присваивая им корректные алреса. Второй вид наиболее распространенных ошибок порождается простым неумением работать с указателями. Рассмотрим пример. /* Осторожно — мины! */ Е1пс1иде <эее(1о.п> 1пс та1п(чей) ( 1пс х, *р; х=10 р = х; рт1пег("ео", *р]; теецтп О; ) При вызове функции рх1пее () число ! 0 ис будет выведено иа экран. Вместо этого вы получите совершенно непредсказуемое значение, поскольку присваивание Щр=х; является неверным. Згаг оператор присваивает число (О указателю р.
Однако указатель р должен содержать адрес, а ие значение. Исправить программу можно следующим об(жзом. $р —" ьх; Ешс одна разновидность ошибок вызывается неверными предположениями о размещении переменных в памяти компьютера.
Программист ие может заранее знать, где именно будут размещены его данные, окажутся ли они в том же месте при следующем запуске программы, и будут ли разные камни/гяторы обрабатывать их одина- Часть (. Основы языка С++( подмножество С ково. Имепно поэтому любые сравнения указателей, которые ссылазотся на разные объекты, нс имеют смысла. Рассмотрим пример. с)заг в[80), У[80); с)заг *р1, *р2з р1 = вз р2 = уз (з(р1 < р2) Этот фрагмент иллюстрирует типичную ошибку, связанную со сравнением указателей.
(В очень редких ситуациях такие сравнения позволяют определить взаимное местоположение переменных. И все же это скорее исключение, чем правило.) Аналогичные ошибки возникают, когда программист считает, будто два смежных массива можно индексировать одним указателем, пересекая границу межлу ними. Рассмотрим следуюший фрагмент программы. (пе г(гас[10), весопс([10)з ).пс *р, р = Гьгвез бог(с=оз Е<20з ++С) *р++ Не стоит рисковать, инициализируя массивы подобиым образом. Даже если в некоторых ситуациях этот способ сработает, пет никакой гарантии, что массивы я1кве и весома всегда будут располагаться в соседних ячейках памяти.