Листинг А.2. ( malloc-use.c ) Пример работы с динамической памятью
/ * Использование функций работы с динамической памятью. */
/* Программе передается один аргумент, определяющий
размер массива. Этот массив состоит из указателей
на (возможно) выделенные буферы памяти.
В процессе работы программы ей можно задавать
следующие команды:
выделение памяти -- а <���индекс> <���размер_буфера>
освобождение памяти -- d <���индекс>
чтение памяти -- r <���индекс> <���смещение>
запись в память -- w <���индекс> <���смещение>
выход -- q
Ответственность за соблюдение правил доступа
к динамической памяти лежит на пользователе. */
#ifdef MTRACE
#include
#endif /* MTRACE */
#include
#include
#include
/* Выделение памяти указанного размера. */
void allocate(char** array, size_t size) {
*array = malloc(size);
}
/* Освобождение памяти. */
void deallocate(char** array) {
free((void*)*array);
}
/* Чтение указанной ячейки памяти. */
void read_from_memory(char* array, int position) {
volatile char character = array[position];
}
/* Запись в указанную ячейку памяти. */
void write_to_memory(char* array, int position) {
array[position] = 'a';
}
int main{int argc, char* argv[]) {
char** array;
unsigned array_size;
char command[32];
unsigned array_index;
char command_letter;
int size_or_position;
int error = 0;
#ifdef MTRACE
mtrace();
#endif /* MTRACE */
if (argc != 2) {
fprintf(stderr, "%s: array-size\n", argv[0]);
return 1;
}
array_size = strtoul(argv[1], 0, 0);
array = (char**)calloc(array_size, sizeof(char*));
assert(array != 0);
/* Выполнение вводимых пользователем команд. */
while (!error) {
printf("Please enter a command: ");
command_letter = getchar();
assert(command_letter != EOF);
switch (command_letter) {
case 'a':
fgets(command, sizeof(command), stdin);
if (sscanf(command, "%u %i", &array_index,
&size_or_position) == 2 &&
array_index < array_size)
allocate(&(array[array_index]), size_or_position);
else
error = 1;
break;
case 'd':
fgets(command, sizeof(command), stdin);
if (sscanf(command, "%u", &array_index) == 1 &&
array_index < array_size)
deallocate(&(array[array_index]));
else
error = 1;
break;
case 'r':
fgets(command, sizeof(command), stdin);
if (sscanf(command, "%u %i", &array_index,
&size_or_position) == 2 &&
array_index < array_size)
read_from_memory(array[array_index], size_or_position);
else
error = 1;
break;
case 'w':
fgets(command, sizeof(command), stdin);
if (sscanf(command, "%u %i", &array_index,
&size_or_position) == 2 &&
array_index < array_size)
write_to_memory(array[array_index], size_or_position);
else
error = 1;
break;
case 'q':
free((void*)array);
return 0;
default:
error = 1;
}
}
free((void*)array);
return 1;
}
Теперь, когда мы знаем, как искать ошибки в программах, настало время разобраться, как ускорить выполнение программы. Профайлер gprof
позволяет определить, какие функции требуют наибольших вычислительных ресурсов и тем самым являются кандидатами на оптимизацию. Профилирование полезно также при отладке, поскольку с помощью этого метода можно установить, какие функции вызываются чаще, чем нужно.
Для получения профильной информации необходимо следовать такому алгоритму.
1. Скомпилируйте и скомпонуйте программу с опциями профилирования.
2. Запустите программу, чтобы сгенерировать профильные данные.
3. Вызовите утилиту gprof
для отображения и анализа профильных данных.
А.3.1. Простейший калькулятор
Для иллюстрации методики профилирования мы напишем простейшую программу- калькулятор. Чтобы программа выполнялась нетривиальным образом, заставим ее работать с унарными числами, чего не встречается в реальных калькуляторах. Код программы приведен в конце приложения.
Значение унарного числа представляется аналогичным количеством символов. Например, число 1 — это "x", 2 — "xx", 3 — "xxx" и т.д. Вместо символов "x" программа использует связный список, количество элементов которого соответствует значению числа. В файле number.c
содержатся функции, позволяющие создавать число 0, добавлять единицу к числу, вычитать единицу из числа, а также складывать, вычитать и умножать числа. Есть функция, которая преобразует строку, содержащую неотрицательное десятичное число, в унарное число. Другая функция преобразует унарное число в значение типа int. Сложение реализуется путем последовательного добавления единицы, вычитание — путем последовательного отнимания единицы, а умножение — путем многократного сложения. Функции even()
и odd()
возвращают унарный эквивалент единицы тогда и только тогда, когда их единственный операнд является соответственно четным или нечетным числом. В противном случае возвращается унарный эквивалент нуля. Обе функции взаимно рекурсивны. Например, число является четным, если оно равно нулю или если число, на единицу меньшее, является нечетным.
Читать дальше