Вопрос2. Основные элементы отладки программ. Пошаговое выполнение программы. Точки останова и окна просмотра
Командой Go to cursor пользуются для автоматического запуска программы с остановом в той строке исходного текста, где находится курсор. Обычно такой режим используется при отладке программы. Также для отладочных целей используется пошаговое выполнение программы. В этом режиме очередное нажатие функциональной клавиши F7 или F8 приводит к останову после выполнения очередной строки исходного текста. Разница между этими двумя режимами заключается в исполнении строк, содержащих вызов пользовательской функции. Нажатие F7 приводит к тому, что пошаговое исполнение сохраняется и в вызываемой функции. В отличие от этого нажатие F8 приводит к автоматическому выполнению вызываемой функции и останову после возврата из нее.
Билет 14
Динамический запрос и освобождение памяти во время выполнения программы. Функции coreleft, malloc, free.
Динамические массивы.
К динамическим массивам относятся массивы, память под которые выделяется работающей программе по запросам, предусмотренным программистом. Не следует путать их с локальными массивами функций, память под которые автоматически выделяется при обращении к функции и также автоматически возвращается при выходе из функции. Память, выделенная под динамические массивы, освобождается в результате вызова специальных процедур, которые должны быть предусмотрены в тексте программы.
Пусть q – указатель на одномерный массив с элементами типа type_q. Тогда запрос на выделение памяти без ее предварительной очистки выполняется с помощью функции malloc:
q=(type_q *)malloc(n_byte);
Приведение к типу данных потребовалось потому, что функция malloc возвращает указатель типа void. Аргументом функции malloc является запрашиваемое количество байт. Необходимо иметь в виду, что данные типа int в 16-битной системе программирования (например, BC 3.1 под управлением MS-DOS) занимают по 2 байта, а в 32-битной среде типа BCB – по 4 байта.
Аналогичный запрос на выделении памяти с ее предварительной очисткой выполняется с помощью функции calloc:
q=(type_q *)calloc(n_el,sizeof(type_q));
В отличие от предыдущей функции здесь уже два аргумента – количество элементов массива (n_el) и длина каждого элемента в байтах sizeof(type_q).
Прототипы обеих функций находятся в заголовочных файлах alloc.h и stdlib.h. Если по каким-то причинам память выделить не удалось, каждая из функций возвращает нулевой указатель (q==NULL).
После выделения блока памяти по malloc или calloc его можно перераспределить, изменив ранее объявленную длину:
q=(type_q)realloc(q,new_len);
Если новая длина больше предыдущей, то содержимое массива q копируется в начало нового блока памяти. Если новая длина меньше предыдущей, то в новом блоке сохраняются значения только начала старого массива. Если new_len=0, то это эквивалентно освобождению занимаемого блока памяти.
После того, как массив q будет использован и больше не понадобится, выделенную память надо возвратить с помощью функции free:
free(q);
Освобождение памяти не сбрасывает указатель q, поэтому с целью предупреждения возможных ошибок в дальнейшем его следует обнулить (операционная система Windows блокирует запись по нулевому адресу):
q=NULL; //или q=0;
Некоторое представление о работе описанных функций дает следующий пример:
#include <alloc.h>
#include <stdio.h>
#include <conio.h>
#include <string.h>
void main()
{ int j,*p,s;
char *str="abcdefghijk",*s1;
p=(int *)malloc(4000); //запрос "грязной" памяти
for(s=0,j=0; j<1000; j++) s += p[j];
printf("s=%d",s); //улика - память "грязная"
for(j=0; j<1000; j++) p[j]=j; //роспись выделенной памяти
printf("\np[500]=%d",p[500]); //выборочная проверка
free(p); //освобождение памяти
p=(int *)calloc(1000,sizeof(int)); //запрос чистой памяти
for(s=0,j=0; j<1000; j++) s += p[j];
printf("\ns=%d",s); //алиби - память чистая
free(p); //освобождение памяти
s1=(char *)calloc(20,1); //запрос памяти под строку
strcpy(s1,str); //копирование данных
printf("\ns1=%s",s1); //вывод старой строки
s1=(char*)realloc(s1,8); //перераспределение памяти
s1[5]=0x0; //признак конца новой строки
printf("\ns1=%s",s1); //вывод новой строки
getch();
}
//=== Результат работы ===
s=-2138551277
p[500]=500
s=0
s1=abcdefghijk
s1=abcde
В языке C++ появились дополнительные средства для запроса и освобождения памяти:
q = new type_q; //запрос памяти под скалярную переменную
q = new type_q[n_el]; //запрос памяти под массив из n_el элементов
delete q; //освобождение памяти из-под скаляра
delete []q; //освобождение памяти из-под массива
Динамическое выделение памяти под скалярную переменную можно совместить с ее инициализацией:
int v=new int(5); //после выделения памяти v=5
Память, выделяемую с помощью оператора new под динамические массивы, таким образом инициализировать нельзя.
2. Позиционные системы счисления. Двоичная, восьмеричная и шестнадцатеричная системы. Функции преобразования itoa, ltoa. Ввод и вывод шестнадцатеричных данных.
3. Довольно полезное преобразование выполняют функции itoa и ltoa. Первый их аргумент – числовое значение типа int или long. Вторым аргументом является строковый массив (или указатель на строку), куда записывается результат преобразования. А третий аргумент, значение которого находится в диапазоне от 2 до 36, определяет основание системы счисления, в которую преобразуется значение первого аргумента.
4. Форматные указатели %o, %x или %X позволяют вводить восьмеричные или шестнадцатеричные числа как с соответствующими префиксами, так и без них.
Для вывода однобайтовых целочисленных данных со знаком (типа char) можно пользоваться одним из следующих форматов – %o, %0x, %0X, %i, %d, %ho, %hx, %hX, %hi, %hd
В потоковом выводе тоже имеются средства управления форматом вывода числовых результатов. Однако научиться пользоваться ими почти так же сложно, как и овладеть нюансами работы с форматными указателями. Дело в том, что потоки вывода в языке C++ организованы двумя способами – старым и новым. Поэтому в управлении потоком вывода можно встретить различные синтаксические конструкции – традиционные функции (hex – установка режима вывода шестнадцатеричного числа, setw – установка ширины поля вывода, setprecision – установка количества отображаемых цифр и т.п.) и более современные обращения к методам класса (cout.fill(…), cout.width(...)). Некоторые из установленных режимов действуют только на вывод следующего значения, другие сохраняют свое действие до следующей установки. С некоторыми деталями управления форматами числовых данных в потоковом выводе можно познакомиться на примере 4 из следующего параграфа.
Примеры программ вывода числовых данных
Пример 1. Вывод однобайтовых числовых значений
#include <iostream.h>
#include <stdio.h>
#include <conio.h>
int main()
{ char ch1 = 69;
char ch2 = -128;
unsigned char uch1 = 70;
unsigned char uch2 = 129;
__int8 i8_1 = 71;
__int8 i8_2 = -127;
printf("\nch1=%d ch2=%d",ch1,ch2);
printf("\nuch1=%u uch2=%u",uch1,uch2);
printf("\ni8_1=%i i8_2=%i\n",i8_1,i8_2);
cout << "ch1=" << ch1 << endl;
cout << "ch1=" << (int)ch1 << endl;
cout << "ch2=" << ch2 << endl;
cout << "ch2=" << (int)ch2 << endl;
cout << "i8_1=" << i8_1 << endl;
cout << "i8_1=" << (int)i8_1 << endl;
cout << "i8_2=" << i8_2 << endl;
cout << "i8_2=" << (int)i8_2 << endl;
getch();
return 0;
}
//=== Результаты работы ===
ch1=69 ch2=-128
uch1=70 uch2=129
i8_1=71 i8_2=-127
ch1=E
ch1=69
ch2=А
ch2=-128
i8_1=G
i8_1=71
i8_2=Б
i8_2=-127
В примере 2 однобайтовые числовые значения выданы по формату в виде десятичных, восьмеричных и шестнадцатеричных данных (основание системы записано в круглых скобках после самого значения).
Пример 3. Вывод коротких и длинных вещественных данных
#include <stdio.h>
#include <conio.h>
int main()
{
float f1=3.14159265, f2=20000000;
double d1=3.14159265, d2=20E+125;
printf("\nf1=%10.8f f2=%f", f1,f2);
printf("\nf1=%e f2=%e", f1,f2);
printf("\nf1=%g f2=%g", f1,f2);
printf("\nd1=%12.9f d2=%g", d1,d2);
printf("\nd1=%12.9e d2=%G", d1,d2);
getch();
return 0;
}
//=== Результаты работы ===
f1=3.14159274 f2=20000000.000000
f1=3.141593e+00 f2=2.000000e+07
f1=3.14159 f2=2e+07
d1= 3.141592650 d2=2e+126
d1=3.141592650e+00 d2=2E+126
Пример 4. Управление форматом при выводе в поток
#include <iostream.h>
#include <iomanip.h>
#include <conio.h>
int main()
{ int i=15, j=6, k;
float v=1.23456, x=3.149;
cout << hex;
cout << "i=" << i << " j=" << j << " x=" << x << " v=" << v;
cout.fill('*');
cout << "\ni=" << setw(6) << i << " j=" << j << endl;
cout << setprecision(3) << x << " v=" << v << endl;
cout.width(10);
cout << x << ' ' << j << endl;
for(k=0; k<8; k++)
cout << setprecision(k) << v << endl;
getch();
return 0;
}
//=== Результаты работы ===
i=f j=6 x=3.149 v=1.23456
i=*****f j=6
3.15 v=1.23
******3.15 6
1.23456
1.2
1.23
1.235
1.2346
1.23456
1.23456
Билет 15