Что произойдет, если мы откомпилируем и запустим ее?
$ сс pick.с -о pick
$ pick *.с
Попробуем
Ошибка при обращении к памяти - сделан дамп
Катастрофа!
$
Сообщение "Ошибка при обращении к памяти" свидетельствует о том, что ваша программа пыталась работать с недозволенной областью памяти. Обычно в таком случае указатель содержит неправильное значение. "Ошибка
адресации шины" другое диагностическое сообщение со сходным значением, часто обусловленное просмотром бесконечной строки. "Сделан дамп памяти" означает, что ядро сохранило состояние вашей выполняемой программы в файле
core
текущего справочника. Вы также можете заставить программу сделать дамп памяти, напечатав ctl-\, если она выполняется как фоновая, или с помощью команды
kill -3
, если она основная.
Существуют две программы
adb
и
sdb
, назначение которых разбираться в "посмертной выдаче". Подобно большинству отладчиков, они "хитроумны", сложны и без них трудно обойтись. Программа
adb
есть в седьмой версии системы, a
sdb
доступна в более поздних версиях.
Из-за ограниченного объема книги мы лишь частично покажем вам применение каждой программы, а именно распечатаем содержимое стека, т.е. выведем функцию, выполнявшуюся при аварийном завершении программы, функцию, которая ее вызывала, и т.д. Первая функция, указанная в распечатке стека, это то место, где находилась программа, когда она была аварийно завершена.
Чтобы получить распечатку стека с помощью
adb
, нужно ввести команду $C:
$ adb pick core
Вызывает adb
$C
Запрос содержимого стека
~_strout(0175722,011,0,011200)
adjust: 0
fillch: 060542
__doprnt(0177345,0176176,011200)
~fprintf(011200,0177345)
iop: 01120
fmt: 0177345
args: 0
~pick(0177345)
s: 0177345
~main(035,0177234)
argc: 035
argv: 0177234
i: 01
buf: 0
ctl-d
Завершение
$
Здесь речь идет о том, что
main
была вызвана из
pick
, которая вызвала
fprintf
, а она в свою очередь вызвала
__doprnt
, вызвавшую
_strout
. Так как
__doprnt
не упомянута где-либо в
pick.с
, ошибка должна быть где-то в
fprintf
или выше. (Строки после каждой функции в распечатке показывают значения локальных переменных.
$С
подавляет данную информацию так же, как сама
$С
делает это в некоторых версиях
adb
.) Попытаемся теперь сделать то же самое с помощью
sdb
:
$ sdb pick core
Предупреждение: 'a.out не компилируется с -g
lseek: address 0xa64
Функция, где программа аварийно завершилась
*t
Запрос распечатки стека
lseek
fprintf(6154,2147479154)
pick(2147479154)
main(30,2147478988,2147479112)
*q
Выход
$
Информация размещена по-иному, но есть общая основа:
fprintf
. (Распечатка стека другая, так как это сделано на машине VAX-11/750, на которой стандартная библиотека ввода вывода реализована иначе.) И если мы взглянем на вызов
fprintf
в неправильной версии
pick
, то обнаружим некорректность:
fprintf("%s?", s);
Здесь нет
stderr
, так что строка формата используется как ссылка к
FILE
, и, конечно, получается хаос.
Мы показали вам типичную ошибку, которая является скорее результатом просмотра, а не неправильного программирования. Искать подобные ошибки при вызове функции с неверными аргументами можно также с помощью верифицирующей программы для Си
lint(1)
. Эта программа рассматривает Си-программы с точки зрения наличия ошибок, аспектов переносимости и сомнительных конструкций. Если мы запустим
Это означает, что первый аргумент в стандартной библиотеке определен иначе, чем в строке 28 вашей программы. Таким образом дана точная информация о том, что неверно.
Программа
lint
, с одной стороны, указывает на недостатки в вашей программе, а с другой выдает много не относящихся к делу сообщений, которые мы выше опустили, и нужен некоторый опыт, чтобы уметь разбираться, какие из них необходимы, а какие следует игнорировать. Однако имеет смысл постараться, так как
lint
помогает обнаружить некоторые ошибки, которые человек увидеть практически не может. После длительного редактирования всегда стоит запустить
lint
и убедиться в том, что каждое выдаваемое этой программой предупреждение вам понятно.
6.7 Пример:
zap
Программа
zap
, которая избирательно уничтожает процессы, отличается от той, что была представлена в виде файла
shell
в гл. 5. Главная проблема данной версии скорость. Она создает много процессов и поэтому работает медленно, что недопустимо для программы, уничтожающей процессы с ошибками. Если переписать
zap
на Си, ее быстродействие повысится. Мы, однако, снова воспользуемся
ps
, чтобы найти информацию о процессе. Это намного легче, чем выуживать информацию из ядра, и, кроме того, мы имеем переносимый вариант. Программа
zap
открывает программный канал, входной поток для которого берется из
ps
, и читает из него, как из файла. Функция
popen(3)
аналогична
fopen
, за исключением того, что первый аргумент является командой, а не именем файла. То же самое справедливо и для
pclose
, но здесь она нам не нужна.
/* zap: interactive process killer */
#include <stdio.h>
#include <signal.h>
char *progname; /* program name for error message */
char *ps = "ps -ag"; /* system dependent */
main(argc, argv)
int argc;
char *argv[];
{
FILE *fin, *popen;
char buf[BUFSIZ];
int pid;
progname = argv[0];
if ((fin = popen(ps, "r")) == NULL) {
fprintf(stderr, "%s: can't run %s\n", progname, ps);
exit(1);
}
fgets(buf, sizeof buf, fin); /* get header line */