Вопрос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

Наши рекомендации