Электронный учебно-методический комплекс по учебной дисциплине «Системное программирование» для специальностей 1-40 01 01 «Программное обеспечение информационных технологий», 6-05-0612-01 «Программная инженерия»
.pdfдинамическая память - выделяется при вызове функции и освобождается при выходе из функции.
Объекты, размещаемые в статической памяти, описываются с атрибутом static и могут иметь любой атрибут области действия. Значения локальных статических объектов сохраняются при повторном вызове функции. Глобальные объекты всегда являются статическими. Атрибут static, использованный при описании глобального объекта, предписывает ограничение области его применимости только в пределах остатка текущего файла. Таким образом, в языке С ключевое слово static имеет разный смысл для локальных и глобальных объектов. В динамической памяти могут размещаться только локальные объекты, объявленные в теле функции. Такие объекты существуют временно на этапе активности функции.
Имеется две разновидности динамической памяти:
автоматическая память (атрибут auto) - объекты располагаются в стековой памяти;
регистровая память (атрибут register) - объекты располагаются в регистрах общего назначения, а при нехватке регистров - в стековой памяти (размер объекта не должен превышать разрядности регистра).
По умолчанию, локальные объекты, объявленные в теле функции, имеют атрибут класса памяти auto. Регистровая память позволяет увеличить быстродействие программы, но к размещаемым в ней объектам неприменима операция адресации & (см. 3.11).
void swap(int *x, int *y) { register int t;
t=*x, *x=*y, *y=t;
}
Понятие области действия приходится учитывать при использовании многофайловых программных комплексов [1]:
/************************ Файл f1.c *********************/
static void f0(); /* f0 доступна только в файле f1.c */ extern int n; /* Переменная n определена в файле f2.c */ char x[]="n"; /* Глобальная переменная без атрибута static */
void f2() { /* Функция f2 доступна из всех файлов */ printf("\nЗначение %s=%d",x,n);
f0(); /* Вызов f0 из текущего файла f1.c */
}
void f0() {
printf("\n Функция f0 из файла f1.c");
}
61
/************************ Файл f2.c *********************/
static void f0() { /* f0 доступна только в файле f2.c */ printf("\n Функция f0 из файла f2.c");
}
int n=10; /* Глобальная переменная без атрибута static */
extern char x[]; /* Массив x определен в файле f1.c */
void main() {
f2(); /* Вызов f2 из файла f1.c */ printf("\n Справедливо ли (%s==%d) ?",x,n); f0(); /* Вызов f0 из текущего файла f2.c */
}
1.5.3 Инициализации объектов программ
Любые объекты, кроме масивов, структур и объединений, имеющих атрибут auto, а также формальных параметров, при определении могут получать начальные значения - константные выражения. Признак инициализации - символ '=' в поле оператора описания объекта.
Начальные значения записываются по следующим правилам: а) основные типы данных
int x=0; float y=3.14; char z='\0'; int *h=&x;
char *u=(char *)&y; int a=sizeof(float);
int print(void), (*prnt)(void)=print;
б) массивы
int |
t[10]={1,2,3,4,5}; |
|
|
|
int |
e[]={1,2,3,4,5}; |
|
|
|
char x[]={'Y','E','S','\0'}; |
/* |
Эквивалентные |
*/ |
|
char x[]="YES"; |
/* |
описания |
*/ |
|
char *x="YES"; |
/* |
массива символов */ |
||
(список значений - в фигурных скобках, недостающие значения заменяются нулями, если размер массива опущен, то он определяется фактическим числом начальных значений, массив символов может быть инициализирован строковой константой);
в) структуры
62
struct part { int code; char *name; };
struct part x={121,"Блок питания"}; struct part y[]= {
{10,"Болт"}, {20,"Гайка"},
{30,"Шайба"}
};
struct part z[10]= { {11}, {12}, {13}
};
(список значений каждой структурной переменной может заключаться в фигурные скобки, значения элементам структуры присваиваются в порядке размещения элементов в определении структурного типа, список значений может быть неполным, тогда оставшиеся поля заполняются нулями).
Перепишем ранее рассмотренный пример простейшего калькулятора [1]:
#include <stdio.h> #include <math.h> #include <conio.h> #include <ctype.h>
char f[]="\n %s(%lf)=%lf";
struct { char *fn;
double (*fa)();
} fd[]={{"Sin",sin}, {"Cos",cos}, {"Exp",exp}, {"Log",log}, {"sQrt",sqrt}, {"Tan",tan}
};
void main() { double x;
int n=sizeof(fd)/sizeof(*fd); int in[256], i,j,k;
for (i=0; i<256; in[i++]=n); for (i=0; i<n; i++)
for (j=0; (k=fd[i].fn[j])!=0; j++) if (!islower(k)) {
in[k]=in[tolower(k)]=i;
63
break;
}
while (sound(1000), printf("\n x-? "), nosound(), scanf("%lf",&x)) {
for (;;) {
printf("\n x=%lf, f()-?",x); switch(k=getch()) {
case 27 : goto cont;
default : if ((i=in[k])<n) printf(f,fd[i].fn,x,(*(fd[i].fa))(x)); else {
sound(100);
printf("\n Набор функций:"); for (i=0; i<n; i++)
printf("\n %s",fd[i].fn); nosound();
}
}
}
cont:;
}
}
1.5.4 Управляемая память
Любой именованный объект программы размещается в статически либо автоматически распределяемой памяти. Статический объект размещается во время запуска программы и существует в течение всего времени ее выполнения. Автоматический объект размещается каждый раз при входе в его блок и существует только до момента выхода из блока. Часто возникает потребность управляемого размещения объектов в памяти в соответствии с алгоритмом решения задачи без привязки к блокам программы. В языках С и С++ такие объекты могут адресоваться только косвенно по значению указателя. Указатель может иметь при этом имя, но адресуемый им объект является безымянным. Область памяти для размещения объектов может быть получена запросом к операционной системе на выделение блока требуемого размера либо назначена на место размещения некоторого известного объекта достаточного размера. Управление размещением объектов осуществляется операциями захвата и освобождения памяти. В языке С для этих целей приходится пользоваться библиотечными функциями. Например, в файле alloc.h декларированы функции:void *malloc(unsigned nbytes) - возврат указателя на выделенную область размером nbytes (NULL при недостатке памяти или nbytes==0);
64
void free(void *block_pointer) - освобождение захваченной памяти по заданному адресу.
Операции захвата и освобождения памяти в стиле языка С имеют вид:
указатель_на_объект=malloc(sizeof(атрибуты_типа_объекта)); free(указатель_на_объект);
Пример программы с работой в управляемой памяти:
#include <stdio.h> #include <stdlib.h> #include <mem.h>
/* Процедура сортировки обменным методом */
int excngs(void *base, int number, int width,
int fcp(const void *x,const void *y)) { char *x,*y,*t;
int i,j,n;
t=(char *)malloc(width); if (t) {
n=number-1; do {
for (i=j=0; i<n; i++) { x=(char *)base+i*width; y=x+width;
if (fcp(x,y)>0) { memcpy(t,x,width); memcpy(x,y,width); memcpy(y,t,width); j=n;
}
}
} while (j); free(t);
}
return (t!=NULL);
65
}
typedef struct {
int attr, app, group, obj; } TEST;
int fcp(const void *x,const void *y) { TEST *X=(TEST *)x,
*Y=(TEST *)y; return(X->attr-Y->attr);
}
/* Исходный массив структур */
TEST x[]={
{-1, 1, 1, 1}, {-10, 2, 1, 1},
{-2, 3, 1, 1},
{-3, 4, 1, 1},
{-1, 5, 1, 1}
};
/* Печать массива структур */
void print(char *title) { int n=sizeof(x)/sizeof(*x); printf("\n %s",title);
for (int i=0; i<n; i++) printf("\n %3d) %6d %6d %6d %6d",
i,x[i].attr,x[i].app,x[i].group,x[i].obj);
printf("\n");
}
/* Тестовая программа */
void main () { print("Исходный массив:");
if (excngs(x,sizeof(x)/sizeof(*x),sizeof(*x),fcp))
66
print("Массив после сортировки:");
}
Безопасное использование управляемой памяти требует обязательной проверки успешности выделения памяти [1].
1.6 ПРЕПРОЦЕССОР ЯЗЫКА C
1.6.1 Возможности препроцессора и его вызов
Препроцессор - программа предварительной обработки исходного текста программы перед этапом компиляции. Способ включения препроцессора в систему программирования определяется стилем ее реализации. Например, в системе программирования Turbo-C препроцессор совмещен с компиляторами TC и TCC. Кроме этого, имеется и автономный пакетный препроцессор CPP. Чаще всего препроцессор автоматически вызывается на этапе компиляции, если в исходном тексте обнаружен хотя бы один препро цессорный оператор. Признаком препроцессорного оператора в языке C является символ '#'(обычно в начале строки). Такой оператор заканчивается символом перевода на новую строку '\n'. При необходимости продолжения оператора в следующей строке текущую строку должен завершать символ '\'.
Возможности препроцессора языка C:
лексемное замещение идентификаторов;
макрозамещение;
включение файлов исходного текста;
условная компиляция;
изменение нумерации строк и текущего имени файла [1].
1.6.2 Операторы лексемного замещения идентификаторов
Оператор определения значения идентификатора:
#define идентификатор строка
В результате каждое вхождение в исходный текст элемента "идентификатор" заменяется на значение элемента "строка":
#define L_bufs 2048 #define binary int
#define WAIT ffluch(stdin); getch() #define BEEP sound(800);\
67
delay(100);nosound()
Лексемное замещение весьма удобно для сокращения записи повторяющихся фрагментов текста и определения символических констант:
#define YES 1 #define NO 2 #define ESC 27 #define Enter 30
Примеры использования:
if (x==ESC) break; BEEP;
return(YES);
Оператор отмены определения идентификатора
#undef идентификатор
Далее по исходному тексту можно назначить новое значение такого идентификатора. Функцию оператора препроцессора #define по отношению к именованию типов объектов может выполнять оператор переопределения типа компилятора языка C typedef (п. 1.2.7) [1].
1.6.3 Макрозамещение
Макрозамещение - обобщение лексемного замещения посредством параметризации строки оператора #define в виде:
#define идентификатор(параметр1,... ) строка
(между элементом "идентификатор" и символом '(' пробелы не допускаются). Такой вариант оператора #define иногда называют макроопределением
(макросом). Элемент "строка" обычно содержит параметры, которые будут заменены препроцессором фактическими аргументами так называемой макрокоманды, записываемой в формате
идентификатор(аргумент1,... )
Пример макроопределения и макрокоманд:
#define P(X) printf("\n%s",X)
char *x;
/* Использование макроопределения P(X) */
P(x);
P(" НАЧАЛО ОПТИМИЗАЦИИ");
68
/* Эквивалентные операторы */
printf("\n%s",x);
printf("\n%s"," НАЧАЛО ОПТИМИЗАЦИИ");
Идентификаторы параметров в строке макроопределений сложных выражений рекомендуется заключать в круглые скобки:
#define МАХ(A,B) ((A)>(B)? (A):(B)) #define UP(x) ((x)-'a'+'А') #define LOW(x) ((x)-'A'+'a')
Потребность в круглых скобках возникает при опасности искажения смысла вложенных выражений из-за действия правил приоритета операций. Пример искажения смысла операций:
#define BP(X) X*X
int x,y,z;
x=BP(y+z); <===> x=y+z*y+z; <===> x=y+(z*y)+z;
Очевидно, что ошибки будут и при следующих вариантах:
#define BP(X) (X*X) #define BP(X) (X)*(X)
Безопасный вариант:
#define BP(X) ((X)*(X))
Иногда источником ошибок может быть символ ';' в конце строки макроопределения:
#define BP(X) ((X)*(X));
int x,y,z;
x=BP(z)-BP(y); <===> y=((z)*(z));-((y)*(y));
Макроопределение отменяется оператором #undef.
Идентификаторы макроопределений обычно составляют из прописных букв латинского алфавита. Это позволяет отличать макрокоманды от вызова функций. Например, в Turbo-C построение "далекого" указателя может быть выполнено макрокомандой MK_FP:
#define MK_FP(seg,ofs) \
((void far *)(((unsigned long)(seg)<<16)|(unsigned)(ofs)))
69
Макрокоманда внешне синтаксически эквивалентна операции вызова функции, но смысл их существенно различен. Функция в программе имеется в одном экземпляре, но на ее вызов тратится время для подготовки параметров и передачи управления. Каждая макрокоманда замещается соответствующей частью макроопределения, но потерь на передачу управления нет. Дополнительное отличие: параметры функции контролируются на соответствие типа и могут автоматически преобразовываться в заданный прототипом тип [1].
1.6.4 Оператор включения файлов исходного текста
Имеются два варианта запроса включения в текущий файл содер жимого другого файла. Оператор
#include <имя_файла>
вводит содержимое файла из стандартного каталога (обычно принято именовать его \include\), а оператор
#include "имя_файла"
организует последовательный поиск в текущем, системном и стандарт ном каталогах.
Примеры (Turbo-C MS-DOS):
#include <stdio.h> |
/* |
Стандартные средства |
ввода-вывода */ |
|
#include <alloc.h> |
/* |
Средства распределения памяти */ |
||
#include <dos.h> |
/* |
Обращения к функциям |
ОС */ |
|
#include |
<conio.h> |
/* |
Консольный ввод-вывод */ |
|
#include |
"a:\prs\head.h" |
/* Включение файла пользователя */ |
||
В стандартных каталогах системы программирования помещены операторы описания внутренних функций языка C, системных переменных среды исполнения, структур данных файловой системы, строк диагностических сообщений, определения констант и т.п.
Рекомендуется описания системных объектов включать из стандартных каталогов и размещать их в начале файла исходного текста программы. Системные объекты в результате получают атрибут области действия "глобальный", что устранит неоднозначность их описания. Включаемый оператором #include исходный текст может содержать любые операторы препроцессора [1].
70
