После объявления нестатического локального указателя до первого присвоения он содержит неопределенное значение. (Глобальные и статические локальные указатели при объявлении неявно инициализируются нулем.) Если попытаться использовать указатель перед присвоением ему нужного значения, то скорее всего он мгновенно разрушит программу или всю операционную систему. Это очень досадная ошибка.
При работе с указателями большинство программистов придерживаются следующего важного соглашения: указатель, не ссылающийся в текущий момент времени должным образом на конкретный объект, должен содержать нулевое значение. Нуль используется потому, что С гарантирует отсутствие чего-либо по нулевому адресу. Следовательно, если указатель равен нулю, то это значит, во-первых, что он ни на что не ссылается, а во-вторых — что его сейчас нельзя использовать.
Указателю можно задать нулевое значение, присвоив ему 0. Например, следующий оператор инициализирует р нулем:
char *p = 0;
Дополнительно к этому во многих заголовочных файлах языка С, например, в <stdio.h> определен макрос NULL, являющийся нулевой указательной константой. Поэтому в программах на С часто можно увидеть следующее присваивание:
p = NULL;
Однако равенство указателя нулю не делает его абсолютно «безопасным». Использование нуля в качестве признака неподготовленности указателя — это только соглашение программистов, но не правило языка С. В следующем примере компиляция пройдет без ошибки, а результат, тем не менее, будет неправильным:
int *p = 0;
*p = 10; /* ошибка! */
В этом случае присваивание посредством p будет присваиванием по нулевому адресу, что обычно вызывает разрушение программы.
Во многих процедурах для повышения эффективности программы можно использовать то, что нулевой указатель заведомо считается неподготовленным для использования. Например, можно использовать нулевой указатель как признак конца массива указателей (по аналогии с нулевым терминатором строки). Процедура, использующая массив указателей, таким образом узнает о конце массива. Такой подход иллюстрируется в таком примере. Просматривая список имен, функция search() определяет, есть ли в этом списке заданное имя.
#include <stdio.h>
#include <string.h>
int search(char *p[], char *name);
char *names[] = {
"Сергей",
"Юрий",
"Ольга",
"Игорь",
NULL}; /* Нулевая константа кончает список */
int main(void)
{
if(search(names, "Ольга") != -1)
printf("Ольга есть в списке.\n");
if(search(names, "Павел") == -1)
printf("Павел в списке не найден.\n");
return 0;
}
/* Просмотр имен. */
int search(char *p[], char *name)
{
register int t;
for(t=0; p[t]; ++t)
if(!strcmp(p[t], name)) return t;
return -1; /* имя не найдено */
}
В функцию search() передаются два параметра. Первый из них, p — массив указателей на строки, представляющие собой имена из списка. Второй параметр name является указателем на строку с заданным именем. Функция search() просматривает массив указателей, пока не найдет строку, совпадающую со строкой, на которую указывает name. Итерации цикла for повторяются до тех пор, пока не произойдет совпадение имен, или не встретится нулевой указатель. Конец массива отмечен нулевым указателем, поэтому при достижении конца массива управляющее условие цикла примет значение ЛОЖЬ. Иными словами, p[t] имеет значение ЛОЖЬ, когда p[t] является нулевым указателем. В рассмотренном примере именно это и происходит, когда идет поиск имени «Павел», которого в списке нет.
В программах на С указатель типа char * часто инициализируют строковой константой (как в предыдущем примере). Рассмотрим следующий пример:
char *p = "тестовая строка";
Переменная р является указателем, а не массивом. Поэтому возникает логичный вопрос: где хранится строковая константа «тестовая строка»? Так как p не является массивом, она не может храниться в p, тем не менее, она где-то записана. Чтобы ответить на этот вопрос, нужно знать, что происходит, когда компилятор встречает строковую константу. Компилятор создает так называемую таблицу строк, в ней он сохраняет строковые константы, которые встречаются ему по ходу чтения текста программы. Следовательно, когда встречается объявление с инициализацией, компилятор сохраняет строку «тестовая строка» в таблице строк, а в указатель p записывает ее адрес. Дальше в программе указатель p может быть использован как любая другая строка. Это иллюстрируется следующим примером:
#include <stdio.h>
#include <string.h>
char *p = "тестовая строка";
int main(void)
{
register int t;
/* печать строки слева направо и справа налево */
printf(p);
for(t=strlen(p)-1; t>-1; t--) printf("%c", p[t]);
return 0;
}