- •Материалы для подоговки к экзамену по дисциплине «основы программировния»
- •Тема №1 Основные понятия
- •Способы записи алгоритма
- •Тема №2 Переменные
- •Типы переменных
- •Типы с плавающей точкой
- •Переполнение переменных
- •Постфиксное обозначение типа
- •Шестнадцатеричный и восьмеричный формат
- •Экспоненциальная форма представления чисел
- •Объявление переменных
- •Начальное значение переменной
- •Область видимости переменной
- •Тема №3 Оператор присваивания
- •Ввод-вывод Форматированный вывод
- •Форматированный ввод
- •Непечатные символы
- •Тема №4 Ветвления и логические операторы
- •Оператор Switch
- •Логические операторы
- •Логическое отрицание
- •Логическое и
- •Логическое или
- •Пример: закон де-Моргана.
- •Порядок выполнения логических операторов
- •Тема №5 Циклы
- •Цикл с предусловием
- •Циклы с постусловием
- •Цикл for
- •Вложенные циклы
- •Тема №6 Одномерные массивы
- •Начальная инициализация массива.
- •Размер массива
- •Переполнение массива
- •Пример с одномерным массивом
- •Многомерные статические массивы
- •Пример с многомерным массивом
- •Тема №7 Строки в си. Введение
- •Чтение строк
- •Указатели
- •Определение
- •Арифметика указателей
- •Указатель на указатель
- •Указатели и приведение типов
- •Null pointer - нулевой указатель
- •Пример работы с указателями
- •Тема №8 Указатели и массивы
- •Многомерные массивы и указатели на многомерные массивы
- •Определение макросов
- •Тема №9 Тернарный оператор и оператор запятая
- •Оператор запятая.
- •Сегментация приложения на си
- •Тема №10 Динамическое выделение памяти
- •Освобождение памяти с помощью free
- •Работа с двумерными и многомерными массивами
- •Тема №11 Параметры командной строки
- •Функции
- •Параметры и аргументы
- •Передача аргументов По значению
- •По указателю (ссылке)
- •Передача массива в качестве аргумента
- •Тема №12 Битовые операции
- •Операции побитового сдвига
- •Примеры
- •Вопросы к экзамену по дисциплине «Основы программирования»
Освобождение памяти с помощью free
Теперь рассмотри, как происходит освобождение памяти. Переменная указатель хранит адрес области памяти, начиная с которого она может им пользоваться. Однако, она не хранит размера этой области. Откуда тогда функция free знает, сколько памяти необходимо освободить?
Очевидно, что информация о размере выделенного участка должна где-то храниться. Есть несколько решения этой проблемы.
Можно создать карту, в которой будет храниться размер выделенного участка. Каждый раз при освобождении памяти компьютер будет обращаться к этим данным и получать нужную информацию.
Второе решение более распространено. Информация о размере хранится на куче до самих данных. Таким образом, при выделении памяти резервируется места больше и туда записывается информация о выделенном участке. При освобождении памяти функция free "подсматривает", сколько памяти необходимо удалить.
Функция free освобождает память, но при этом не изменяет значение указателя, о чём нужно помнить.
Работа с двумерными и многомерными массивами
Для динамического создания двумерного массива сначала необходимо создать массив указателей, после чего каждому из элементов этого массива присвоить адрес нового массива.
Для удаления массива необходимо повторить операцию в обратном порядке - удалить сначала подмассивы, а потом и сам массив указателей.
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#define COL_NUM 10
#define ROW_NUM 10
void main() {
float **p = NULL;
unsigned i;
p = (float**) malloc(ROW_NUM * sizeof(float*));
for (i = 0; i < ROW_NUM; i++) {
p[i] = (float*) malloc(COL_NUM * sizeof(float));
}
//Здесь какой-то важный код
for (i = 0; i < ROW_NUM; i++) {
free(p[i]);
}
free(p);
}
Сначала мы создаём массив указателей, а после этого каждому элементу этого массива присваиваем адрес вновь созданного массива. Это значит, что можно
Создавать массивы "неправильной формы", то есть массив строк, каждая из которых имеет свой размер.
Работать по отдельности с каждой строкой массива: освобождать память или изменять размер строки.
Создадим "треугольный" массив и заполним его значениями
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#define SIZE 10
void main() {
int **A;
int i, j;
A = (int**) malloc(SIZE * sizeof(int*));
for (i = 0; i < SIZE; i++) {
A[i] = (int*) malloc((i + 1) * sizeof(int));
}
for (i = 0; i < SIZE; i++) {
for (j = i; j > 0; j--) {
A[i][j] = i * j;
}
}
for (i = 0; i < SIZE; i++) {
for (j = i; j > 0; j--) {
printf("%d ", A[i][j]);
}
printf("\n");
}
for (i = SIZE-1; i > 0; i--) {
free(A[i]);
}
free(A);
_getch();
}
Чтобы создать трёхмерный массив, по аналогии, необходимо сначала определить указатель на указатель на указатель, после чего выделить память под массив указателей на указатель, после чего проинициализировать каждый из массивов и т.д.
calloc
Функция calloc выделяет n объектов размером m и заполняет их нулями. Обычно она используется для выделения памяти под массивы. Синтаксис
void* calloc(size_t num, size_t size);
realloc
Ещё одна важная функция – realloc (re-allocation). Она позволяет изменить размер ранее выделенной памяти и получает в качестве аргументов старый указатель и новый размер памяти в байтах:
void* realloc(void* ptr, size_t size)
Функция realloc может как использовать ранее выделенный участок памяти, так и новый. При этом не важно, меньше или больше новый размер – менеджер памяти сам решает, где выделять память.
Пример – пользователь вводит слова. Для начала выделяем под слова массив размером 10. Если пользователь ввёл больше слов, то изменяем его размер, чтобы хватило места. Когда пользователь вводит слово end, прекращаем ввод и выводим на печать все слова.
#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define TERM_WORD "end"
#define SIZE_INCREMENT 10
void main() {
//Массив указателей на слова
char **words;
//Строка, которая используется для считывания введённого пользователем слова
char buffer[128];
//Счётчик слов
unsigned wordCounter = 0;
//Длина введённого слова
unsigned length;
//Размер массива слов. Для уменьшения издержек на выделение памяти
//каждый раз будем увеличивать массив слов не на одно значение, а на
//SIZE_INCREMENT слов
unsigned size = SIZE_INCREMENT;
int i;
//Выделяем память под массив из size указателей
words = (char**) malloc(size*sizeof(char*));
do {
printf("%d: ", wordCounter);
scanf("%127s", buffer);
//Функция strcmp возвращает 0, если две строки равны
if (strcmp(TERM_WORD, buffer) == 0) {
break;
}
//Определяем длину слова
length = strlen(buffer);
//В том случае, если введено слов больше, чем длина массива, то
//увеличиваем массив слов
if (wordCounter >= size) {
size += SIZE_INCREMENT;
words = (char**) realloc(words, size*sizeof(char*));
}
//Выделяем память непосредственно под слово
//на 1 байт больше, так как необходимо хранить терминальный символ
words[wordCounter] = (char*) malloc(length + 1);
//Копируем слово из буффера по адресу, который
//хранится в массиве указателей на слова
strcpy(words[wordCounter], buffer);
wordCounter++;
} while(1);
for (i = 0; i < wordCounter; i++) {
printf("%s\n", words[i]);
}
_getch();
for (i = 0; i < wordCounter; i++) {
free(words[i]);
}
free(words);
}
Хочу обратить внимание, что мы при выделении памяти пишем sizeof(char*), потому что размер указателя на char не равен одному байту, как размер переменной типа char.
