Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии 


For (инициализация; условие; инкремент)




В этой программе обратите внимание на выражение, управляющее циклом for. Оно приводит к завершению цикла, когда элемент keyword[i][0] содержит указатель на нуль, который интерпретируется как значение ЛОЖЬ. Следовательно, цикл останавливается, когда встречается нулевая строка, которая завершает массив указателей.

Соглашение о нулевых указателях

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

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

float *р = 0; // р — теперь нулевой указатель.

Для тестирования указателя используется инструкция if (любой из следующих ее вариантов).

If(р) // Выполняем что-то, если р — не нулевой указатель.

if(!p) // Выполняем что-то, если р — нулевой указатель.

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

Указатели и 16-разрядные среды

Несмотря на то что в настоящее время большинство вычислительных сред 32-разрядные, все же немало пользователей до сих пор работают в 16-разрядных (в основном, это DOS и Windows 3.1) и, естественно, с 16-разрядным кодом. Эти операционные системы были разработаны для процессоров семейства Intel 8086, которые включают такие модификации, как 80286, 80386, 80486 и Pentium (при работе в режиме эмуляции процессора 8086). И хотя при написании нового кода программисты, как правило, ориентируются на использование 32-разрядной среды выполнения, все же некоторые программы по-прежнему создаются и поддерживаются в более компактных 16-разрядных средах. Поскольку некоторые темы актуальны только для 16-разрядных сред, программистам, которые работают в них, будет полезно получить информацию о том, как адаптировать "старый" код к новой среде, т.е. переориентировать 16-разрядный код на 32-разрядный.

При написании 16-разрядного кода для процессоров семейства Intel 8086 программист вправе рассчитывать на шесть способов компиляции программ, которые различаются организацией компьютерной памяти. Программы можно компилировать для миниатюрной, малой, средней, компактной, большой и огромной моделей памяти. Каждая из этих моделей по-своему оптимизирует пространство, резервируемое для данных, кода и стека. Различие в организации компьютерной памяти объясняется использованием процессорами семейства Intel 8086 сегментированной архитектуры при выполнении 16-разрядного кода. В 16-разрядном сегментированном режиме процессоры семейства Intel 8086 делят память на 16К сегментов.

В некоторых случаях модель памяти может повлиять на поведение указателей и ваши возможности по их использованию. Основная проблема возникает при инкрементировании указателя за пределы сегмента. Рассмотрение особенностей каждой из 16-разрядных моделей памяти выходит за рамки этой книги. Главное, чтобы вы знали, что, если вам придется работать в 16-разрядной среде и ориентироваться на процессоры семейства Intel 8086, вы должны изучить документацию, прилагаемую к используемому вами компилятору, и подробно разобраться в моделях памяти и их влиянии на указатели.

И последнее. При написании программ для современной 32-разрядной среды необходимо знать, что в ней используется единственная модель организации памяти, которая называется одноуровневой, несегментированной или линейной (flat model).

Многоуровневая непрямая адресация

Можно создать указатель, который будет ссылаться на другой указатель, а тот — на конечное значение. Эту ситуацию называют многоуровневой непрямой адресацией (multiple indirection) или использованием указателя на указатель. Идея многоуровневой непрямой адресации схематично проиллюстрирована на рис. 6.2. Как видите, значение обычного указателя (при одноуровневой непрямой адресации) представляет собой адрес переменной, которая содержит некоторое значение. В случае применения указателя на указатель первый содержит адрес второго, а тот ссылается на переменную, содержащую определенное значение.

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

Переменную, которая является указателем на указатель, нужно объявить соответствующим образом. Для этого достаточно перед ее именем поставить дополнительный символ "звездочка"(*). Например, следующее объявление сообщает компилятору о том, что balance — это указатель на указатель на значение типа int.

int **balance;

Необходимо помнить, что переменная balance здесь — не указатель на целочисленное значение, а указатель на указатель на int-значение.

Чтобы получить доступ к значению, адресуемому указателем на указатель, необходимо дважды применить оператор "*" как показано в следующем коротком примере.

// Использование многоуровневой непрямой адресации.

#include <iostream>

using namespace std;

Int main()

{

 int x, *p, **q;

 x = 10;

 p = &x;

 q = &p;

 cout << **q; // Выводим значение переменной x.

 return 0;

}

Здесь переменная p объявлена как указатель на int-значеине, а переменная q — как указатель на указатель на int-значеине. При выполнении этой программы мы получим значение переменной х, т.е. число 10.

Проблемы, связанные с использованием указателей

Для программиста нет ничего более страшного, чем "взбесившиеся" указатели! Указатели можно сравнить с энергией атома: они одновременно и чрезвычайно полезны и чрезвычайно опасны. Если проблема связана с получением указателем неверного значения, то такую ошибку отыскать труднее всего.

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

Поскольку для многих работать с указателями — значит потенциально обречь себя на поиски ответа на вопрос "Кто виноват?", мы попытаемся рассмотреть возможные "овраги" на пути отважного программиста и показать обходные пути, позволяющие избежать изматывающих "мук творчества".

Неинициализированные указатели

Классический пример ошибки, допускаемой при работе с указателями, — использование неинициализированного указателя. Рассмотрим следующий фрагмент кода.

// Эта программа некорректна.

Int main()

{

 int х, *р;

 х = 10;

 *р = х; // На что указывает переменная р?

 return 0;

}

Здесь указатель р содержит неизвестный адрес, поскольку он нигде не определен. У вас нет возможности узнать, где записано значение переменной х. При небольших размерах программы (например, как в данном случае) ее странности (которые заключаются в том, что указатель р содержит адрес, не принадлежащий ни коду программы, ни области данных) могут никак не проявляться, т.е. программа внешне будет работать нормально. Но по мере ее развития и, соответственно, увеличения ее объема, вероятность того, что р станет указывать либо на код программы, либо на область данных, возрастет. В один прекрасный день программа вообще перестанет работать. Способ не допустить создания таких программ очевиден: прежде чем использовать указатель, позаботьтесь о том, чтобы он ссылался на что-нибудь действительное!

Некорректное сравнение указателей

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

char s[80];

char у[80];

char *p1, *р2;

p1 = s;

р2 = у;

if(p1 < р2) . . .

Здесь используется некорректное сравнение указателей, поскольку C++ не дает никаких гарантий относительно размещения переменных в памяти. Ваш код должен быть написан таким образом, чтобы он работал одинаково устойчиво вне зависимости от того, где расположены данные в памяти.

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

int first[101;

int second[10];

int *p, t;

p = first;

for(t=0; t<20; ++t) {

 *p = t;

 p++;

}

Цель этой программы — инициализировать элементы массивов first и second числами от 0 до 19. Однако этот код не позволяет надеяться на достижение желаемого результата, несмотря на то, что в некоторых условиях и при использовании определенных компиляторов эта программа будет работать так, как задумано автором. Не стоит полагаться на то, что массивы first и second будут расположены в памяти компьютера последовательно, причем первым обязательно будет массив first. Язык C++ не гарантирует определенного расположения объектов в памяти, и потому эту программу нельзя считать корректной.

Не забывайте об установке указателей

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

// Эта программа некорректна.

#include <iostream>

#include <cstdio>

#include <cstring>

using namespace std;

Int main()

{

 char s [80];

 char *p1;

 p1 = s;

 do {

  cout << "Введите строку: ";

  gets(p1); // Считываем строку.

  // Выводим ASCII-значения каждого символа.

  while(*p1) cout << (int) *p1++ << ' ';

  cout << ' ';

 }while(strcmp (s, "конец"));

 return 0;

}





Читайте также:

Последнее изменение этой страницы: 2016-03-17; Просмотров: 271; Нарушение авторского права страницы


lektsia.com 2007 - 2018 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.017 с.) Главная | Обратная связь