Linux программирование в примерах - Арнольд Роббинс
Шрифт:
Интервал:
Закладка:
1 /* ch12-memleak.с --- демонстрирует утечки памяти с помощью setjmp()/longjmp(). */
2
3 #include <stdio.h>
4 #include <malloc.h> /* для определения ptrdiff_t в GLIBC */
5 #include <setjmp.h>
6 #include <unistd.h>
7
8 jmp_buf env;
9
10 void f1(void), f2(void);
11
12 /* main --- утечка памяти с помощью setjmp() и longjmp() */
13
14 int main(void)
15 {
16 char *start_break;
17 char *current_break;
18 ptrdiff_t diff;
19
20 start_break = sbrk((ptrdiff_t)0);
21
22 if (setjmp(env) == 0) /* первый раз */
23 printf("setjmp calledn");
24
25 current_break = sbrk((ptrdiff_t) 0);
26
27 diff = current_break - start_break;
28 printf("memsize = %ldn", (long)diff);
29
30 f1();
31
32 return 0;
33 }
34
35 /* f1 --- выделяет память, осуществляет вложенный вызов */
36
37 void f1(void)
38 {
39 char *p = malloc(1024);
40
41 f2();
42 }
43
44 /* f2 --- выделяет память, выполняет longjmp */
45
46 void f2(void)
47 {
48 char *p = malloc(1024);
49
50 longjmp(env, 1);
51 }
Эта программа устанавливает бесконечный цикл, используя setjmp() и longjmp(). Строка 20 использует для нахождения текущего начала кучи sbrk() (см. раздел 3.2.3 «Системные вызовы: brk() и sbrk()»), а затем строка 22 вызывает setjmp(). Строка 25 получает текущее начало кучи; это место каждый раз изменяется, поскольку longjmp() повторно входит в код. Строки 27–28 вычисляют, сколько было выделено памяти, и выводят это количество. Вот что происходит при запуске:
$ ch12-memleak /* Запуск программы */
setjmp called
memsize = 0
memsize = 6372
memsize = 6372
memsize = 6372
memsize = 10468
memsize = 10468
memsize = 14564
memsize = 14564
memsize = 18660
memsize = 18660
...
Память утекает из программы, как через решето. Она работает до тех пор, пока не будет прервана от клавиатуры или пока не закончится память (в этом случае образуется основательный дамп ядра).
Каждая из функций f1() и f2() выделяют память, a f2() выполняет longjmp() обратно в main() (строка 51). Когда это происходит, локальные указатели (строки 39 и 48) на выделенную память пропали! Такие утечки памяти может оказаться трудно отследить, поскольку часто выделяются небольшие размеры памяти, и как таковые, они могут оставаться незамеченными в течение ряда лет[128].
Этот код явно патологический, но он предназначен для иллюстрации нашей мысли: setjmp() и longjmp() могут вести к трудно обнаруживаемым утечкам памяти. Предположим, что f1() правильно вызвал free(). Было бы далеко неочевидно, что память никогда не будет освобождена. В более крупной и более реалистичной программе, в которой longjmp() мог быть вызван лишь посредством if, найти такую утечку становится даже еще труднее.
Таким образом, при наличии setjmp() и longjmp() динамическая память должна управляться посредством глобальных переменных, а у вас должен быть код, который обнаруживает вход через longjmp() (посредством проверки возвращаемого значения setjmp()). Такой код должен затем освободить динамически выделенную память, которая больше не нужна.
В-шестых, longjmp() и siglongjmp() не следует использовать из функций, зарегистрированных посредством atexit() (см. раздел 9.1.5.3 «Функции завершения»).
В-седьмых, setjmp() и longjmp() могут оказаться дорогими операциями на машинах с множеством регистров.
При наличии всех этих проблем вы должны строго рассмотреть дизайн своей программы. Если вам не нужно использовать setjmp() и longjmp(), то, может, стоит обойтись без их использования. Однако, если их использование является лучшим способом структурировать свою программу, продолжайте и используйте их, но делайте это осмотрительно.
12.6. Псевдослучайные числа
Многим приложениям нужны последовательности случайных чисел. Например, игровые программы, имитирующие бросание костей, раздачу карт или вращение барабанов игровой машины, нуждаются в возможности случайного выбора одного из возможных значений. (Подумайте о программе fortune, содержащей большую коллекцию афоризмов; каждый раз при запуске она «случайно» выдает новое высказывание.) Многие криптографические алгоритмы также требуют наличия случайных чисел «высокого качества». В данном разделе описываются различные способы получения последовательностей случайных чисел.
ЗАМЕЧАНИЕ. Природа случайности, генерация случайных чисел и их «качество» являются обширными темами, выходящими за рамки данной книги. Мы предоставляем введение в доступные функции API, но это все, что мы можем сделать Другие источники с более подробной информацией см в разделе 12.9 «Рекомендуемая литература»
Компьютеры по своему строению являются детерминистическими. Одно и то же вычисление с одними и теми же входными данными всегда должно давать одни и те же результаты. Соответственно, они не годятся для генерации истинно случайных чисел, то есть последовательностей чисел, в которых каждое число в последовательности полностью независимо от числа (или чисел), идущих перед ним. Вместо этого разновидности чисел, обычно используемых на программном уровне, называются псевдослучайными числами. То есть в любой данной последовательности номера выглядят независимыми друг от друга, но сама последовательность в целом повторяющаяся. (Эта повторяемость может быть ценным качеством; она обеспечивает детерминизм для программы в целом.)
Многие методы предоставления последовательностей псевдослучайных чисел работают посредством осуществления каждый раз одного и того же вычисления с начальным значением (seed). Сохраненное начальное значение затем обновляется для использования в следующий раз. API предоставляет способ указания нового начального значения. Каждое начальное значение дает одну и ту же последовательность псевдослучайных чисел, хотя различные начальные числа дают (должны давать) различные последовательности.
12.6.1. Стандартный С: rand() и srand()
Стандартный С определяет две связанные функции для псевдослучайных чисел.
#include <stdlib.h> /* ISO С */
int rand(void);
void srand(unsigned int seed);
rand() каждый раз после вызова возвращает псевдослучайное число в диапазоне от 0 до RAND_MAX (включительно, насколько мы можем судить по стандарту C99). Константа RAND_MAX должна быть по крайней мере 32 767; она может быть больше.
srand() дает генератору случайных чисел в качестве начального значения seed. Если srand() никогда не вызывался приложением, rand() ведет себя так, как если бы seed был равен 1.
Следующая программа, ch12-rand.c, использует rand() для вывода граней игральных костей.
1 /* ch12-rand.c --- генерирует игральные кости, используя rand(). */
2
3 #include <stdio.h>
4 #include <stdlib.h>
5
6 char *die_faces[] = { /* Управляет ASCII графика! */
7 " ",
8 " * ", /* 1 */
9 " ",
10
11 " ",
12 " * * ", /* 2 */
13 " ",
14
15 " ",
16 " * * * ", /* 3 */
17 " ",
18
19 " * * ",
20 " ", /* 4 */
21 " * * ",
22
23 " * * ",
24 " * ", /* 5 */
25 " * * ",
26
27 " * * * ",
28 " ", /* 6 */
29 " * * * ",
30 };
31
32 /* main --- выводит N различных граней костей */
33
34 int main(int argc, char **argv)
35 {
36 int nfaces;
37 int i, j, k;
38
39 if (argc !=2) {
40 fprintf(stderr, "usage: %s number-die-facesn", argv[0]);
41 exit(1);
42 }
43
44 nfaces = atoi(argv[1]);
45
46 if (nfaces <= 0) {
47 fprintf(stderr, "usage: %s number-die-facesn", argv[0]);
48 fprintf(stderr, "tUse a positive number!n");
49 exit(1);
50 }
51
52 for (i = 1; i <= nfaces; i++) {
53 j = rand() % 6; /* force to range 0 <= j <= 5 */