Лекция 17, фукнция realloc. Численное решение уравнений (полный конспект без картинок)

Повторение. Классы памяти и выделение памяти

-------------------------------------------------------
|маш. коды | статический класс |   стек   |    куча   |
           | памяти            |
-------------------------------------------------------

Статический класс памяти предназначен для данных, память под которые выделяется только один раз при старте программы: для глобальных и статических переменных и строковых констант.

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

-----------------------
| main | func | func2 |
-----------------------

------------------------
| main | func | printf |
------------------------

func2 завершилась, func вызвала printf. Данные printf хранятся поверх бывших данных func2.

Куча (динамический класс памяти)

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

Управление выделением памяти происходит вручную с помощью функций malloc и free. Время жизни данных не ограничено временем выполнения функции.

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

void *malloc(size_t size);
void free(void *ptr);
void *realloc(void *ptr, size_t new_size);

Пример использования этих функций:

double *arr = (double *) malloc(sizeof(double) * 20);
// (double *) перед функцией или выражением означает, что
// тип этого выражения будет приведён к указателю на double.

... работаем с массивом arr, понимаем, что 20 чисел нам не хватает ...

arr = (double *) realloc(arr, sizeof(double) * 50);

// Теперь arr указывает на массив из 50 чисел, а не 20.
// Первые 20 чисел те же самые.

free(arr);

// Освободили память, её теперь использовать нельзя.

Наивная реализация функции realloc, которая не умеет делать усечение или расширение области памяти, а будет заново её выделять и копировать содержимое старой:

void *myrealloc(void *old_ptr, size_t new_size)
{
    void *new_ptr = malloc(new_size);
    if (NULL == new_ptr) {
        return NULL;
    }
    if (NULL == old_ptr) {
        return new_ptr;
    }
    size_t old_size = get_old_size(old_ptr);
    // На самом деле этой функции не существует, в данном случае
    // она используется лишь для демонстрации. На самом деле,
    // чтобы узнать размер куска памяти, на который указывает
    // malloc, придётся вручную лезть в реестр заполненных и
    // свободных участков памяти, который ведёт malloc.

    memcpy(new_ptr, old_ptr, min(new_size, old_size));
    free(old_ptr);
    return new_ptr;
}

Замечание

int arr[10];
sizeof(arr);  // sizeof(int) * 10
int *ptr = arr;
sizeof(ptr); // sizeof(int *). Размер указателя постоянен и не зависит
        // от размера участка памяти, на который он указывает.

Примеры функций, выделяющих динамическую память

Реализация библиотечной функци strdup, копирующей строку

char *mystrdup(char *src)
{
    size_t sz = strlen(src) + 1;
    char *s = (char *) malloc(sz);
    strcpy(s, src);
    return s;
}

Реализация библиотечной функции calloc, выделяющей память и заполняющей её нулевым байтами.

void *mycalloc(size_t n_elem, size_t elem_size)
{
    void *result = malloc(n_elem * elem_size);
    if (NULL == result) {
        return NULL;
    }
    memset(result, 0, n_elem * elem_size);
    return result;
}

Замечания по работе функции free:

  • Указатель, который передаётся free, обязательно должен быть ранее выделен с помощью malloc. Нельзя, чтобы указатель указывал в середину выделенного куска памяти, только оригинальные указатели на начало выделенной памяти, которые возвращает malloc.
  • Освобождать память с помощью free можно только один раз. Если указатель на кусок памяти передать функции free дважды, это приведёт к ошибке или порче памяти.
  • Если у вас есть несколько указателей на один и тот же кусок памяти в разных местах программы, то при освобождении памяти через один из них пользоваться другими тоже нельзя, так как память будет помечена как свободная вне зависимости от количества указателей, которые на неё указывают.

Метод деления отрезка пополам

Условия корректности:

  1. Функция f имеет различные знаки в точках a и b, то есть \(f(a) * f(b) \le 0\)
  2. Функция f непрерывна при \(x \in [a, b]\)
  3. Функция f монотонна при \(x \in [a, b]\)

Метод состоит в делении текущего отрезка пополам, и выбора того из двух образовавшихся подотрезков, на котором функция меняет знак. Далее получившийся отрезок также делится пополам, и так до тех пор, пока длина получившегося отрезка не станет меньше некоторого заданного параметра погрешности \(\varepsilon\).

_images/bisection.png

Алгоритм

  1. \(c = \frac{a + b}{2}\)
  2. Если \(f(a) * f(c) \le 0\), то b = c
  3. Если \(f(c) * f(b) \le 0\), то a = c
  4. Если \(|a - b| > \varepsilon\), то переходим к шагу 1
  5. Ответ: (a + b) / 2

Число шагов: \(log_2 (\frac{b-a}{\varepsilon} )\)

Метод хорд

TODO Картинка

TODO Условия корректности

Уравнение хорды:

\[y(x) = kx + b = \frac{f(b) - f(a)}{b - a}(x - a) + f(a)\]

из уравнения \(y(c) = 0\) находим точку пересечения:

\[c = a - f(a) \frac{f(b) - f(a)}{b - a}\]

Алгоритм

  1. \(c = a - f(a) \frac{f(b) - f(a)}{b - a}\)
  2. a = b
  3. b = c
  4. Если \(|a - b| > \varepsilon\), перейти к пункту 1
  5. Ответ: b.

Метод Ньютона

На входе: функция и её производная \(f(x), f'(x)\) , начальное приближение \(x_0\), точность \(\varepsilon\)

TODO Картинка

Уравнение касательной:

\[y = kx + b = f'(x_0) (x - x_0) + f(x_0)\]
\[y(c) = 0\]
\[x_1 = x_0 - \frac{f(x_0)}{f'(x_0)}\]