Скачать презентацию Тема Указатели и массивы Лекция 03 10 11 Скачать презентацию Тема Указатели и массивы Лекция 03 10 11

Лекция-08-09.ppt

  • Количество слайдов: 22

Тема Указатели и массивы Лекция 03. 10. 11 г. 1 Тема Указатели и массивы Лекция 03. 10. 11 г. 1

Адресация памяти Ø Будем считать, что оперативная память компьютера представляет собой линейную последовательность пронумерованных Адресация памяти Ø Будем считать, что оперативная память компьютера представляет собой линейную последовательность пронумерованных ячеек памяти – байтов и каждая ячейка памяти имеет адрес. Ø Будем также считать, что компьютер имеет 32 разрядную архитектуру и адреса записываются как 32 -битные коды. Ø Наименьший адрес: 0 x 0000, наибольший – 0 x. FFFF. Ø Адресуемый объем памяти – 232 байт = 4 Гбайт 0 x 00000001 0 x 00000002 ……………. . 4 Гбайт 0 x. FFFFFFFE 0 x. FFFF Лекция 03. 10. 11 г. 2

Адреса объектов программы Ø Каждый объект программы занимает определенный участок оперативной памяти компьютера и, Адреса объектов программы Ø Каждый объект программы занимает определенный участок оперативной памяти компьютера и, следовательно, может быть охарактеризован адресом начала этого участка, который и считается адресом данного объекта. Ø В языке С имеется специальная унарная операция & для получения адреса объекта d в оперативной памяти. Ø Функция printf имеет специальный формат %p для вывода адреса. #include #include int x; short y; char a[256], b[512]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(x) = %pn", &x); printf("addr(y) = %pn", &y); printf("addr(a) = %pn", &a); printf("addr(b) = %pn", &b); printf("addr(fact) = %pn", &fact); system("PAUSE"); return 0; Лекция 03. 10. 11 г. } 3

Размещение в памяти объектов программы fact 0 x 00401300 ? ? байт a 0 Размещение в памяти объектов программы fact 0 x 00401300 ? ? байт a 0 x 004050 DC y 0 x 004051 DC b 0 x 004051 EC 0 x 004053 EC 0 x 1 DC– 0 x 0 DC=0 x 100=256 16 байт 0 x 1 EC– 0 x 1 DC=0 x 010=16 512 байт x 256 байт 0 x 3 EC– 0 x 1 EC=0 x 200=512 16 байт Лекция 03. 10. 11 г. 4

Замечание Некоторые объекты программы не требуют применения к ним операции взятия адреса &, т. Замечание Некоторые объекты программы не требуют применения к ним операции взятия адреса &, т. к. они сами и являются адресами. К их числу относятся функции и массивы, т. е. значением имени функции и значением имени массива является адрес. Доказательство этого: #include #include a означает то же, что и &a int x; b означает то же, что и &b short y; fact означает то же, что и &fact char a[256], b[512]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(x) = %pn", &x); printf("addr(y) = %pn", &y); printf("addr(a) = %pn", a); printf("addr(b) = %pn", b); printf("addr(fact) = %pn", fact); system("PAUSE"); return 0; } Лекция 03. 10. 11 г. 5

О размерах участков памяти, выделяемых объектам Результаты работы программы позволяют предположить, что память объектам О размерах участков памяти, выделяемых объектам Результаты работы программы позволяют предположить, что память объектам программы выделяется блоками, кратными 16 байт. Проведем эксперимент - вместо y определим массив из 8 элементов, а размер массива b увеличим на 1: #include #include int x; short y[8]; char a[256], b[512+1]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(x) = %pn", &x); printf("addr(y) = %pn", y); printf("addr(a) = %pn", a); printf("addr(b) = %pn", b); printf("addr(fact) = %pn", fact); system("PAUSE"); return 0; } Лекция 03. 10. 11 г. 6

Иллюстрация Было: fact Стало: fact 0 x 00401300 ? ? байт a 0 x Иллюстрация Было: fact Стало: fact 0 x 00401300 ? ? байт a 0 x 004050 DC y 0 x 004051 DC b 0 x 004051 EC ? ? байт 16 байт 0 x 004050 DC y 0 x 004051 DC b 256 байт a 0 x 004051 EC 0 x 004053 EC 16 байт x 0 x 004053 FC 16 байт 8 * 2 байта 528 байт 512 байт x 256 байт 513 * 1 байт 16 байт 0 x 3 FC– 0 x 1 EC=0 x 210=528 Лекция 03. 10. 11 г. 7

Адресная арифметика Можно предположить, что адреса являются обычными числами (? ) и могут участвовать Адресная арифметика Можно предположить, что адреса являются обычными числами (? ) и могут участвовать в арифметических операциях. Проверим это: #include #include int x; short y[8]; char a[256], b[512+1]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(x) = %pn", &x); printf("addr(x)+2 = %pn", &x+2); printf("addr(y) = %pn", y); printf("addr(y)-128 = %pn", y-128); printf("addr(a) = %pn", a); printf("addr(a)+2 = %pn", a+2); printf("addr(b) = %pn", b); printf("addr(b)+528 = %pn", b+528); printf("addr(fact) = %pn", fact); printf("addr(fact)+2 = %pn", fact+2); system("PAUSE"); return 0; Лекция 03. 10. 11 г. } 8

Анализ результатов Ø int x: при увеличении адреса на 2 результат увеличился на 0 Анализ результатов Ø int x: при увеличении адреса на 2 результат увеличился на 0 x 404 -0 x 3 FC=8 , т. е. увеличение в 4 раза больше (а размер переменной типа int как раз и = 4 байтам) Ø short y[8]: при уменьшении адреса на 128 результат уменьшился на 0 x 51 DC-0 x 50 DC=0 x 100=256 , т. е. уменьшение в 2 раза больше (а размер переменной типа short как раз и = 2 байтам) Ø char a[256]: при увеличении адреса на 2 результат увеличился на 0 x. DE-0 x. DC=2 , что и требовалось, потому что размер переменной типа char = 1 байту Ø char b[513]: увеличение адреса на 528 в точности дало адрес переменной x. Ø int fact(int n): при увеличении адреса на 2 результат увеличился также на 2, что позволяет предположить, что «тип адреса» функции такой же, что и тип адреса переменной char. Лекция 03. 10. 11 г. 9

Выводы (правила адресной арифметики) 1. К адресу можно прибавлять или из адреса можно вычитать Выводы (правила адресной арифметики) 1. К адресу можно прибавлять или из адреса можно вычитать целые значения. При этом результат операции зависит от типа переменной, адрес которой участвует в операции. Если для данного типа размер переменной равен n байт и к адресу прибавляется i, то величина адреса изменится на i*n. 2. Никакие другие операции к адресам неприменимы, т. е. адреса нельзя умножать, делить, складывать между собой и пр. 3. Исключением является операция вычитания адресов однотипных объектов, результатом которой является целое число, равное количеству объектов данного типа, которые могут разместиться на участке памяти между адресами-операндами. Лекция 03. 10. 11 г. 10

Иллюстрация к вычитанию адресов #include <stdio. h> #include <stdlib. h> int x; short y[8]; Иллюстрация к вычитанию адресов #include #include int x; short y[8]; char a[256], b[512+1]; int fact(int n) { return n ? n * fact(n-1) : 1; } main() { printf("addr(b)-addr(a) = %dn", printf("addr(a)-addr(b) = %dn", printf("addr(y)-addr(a) = %dn", printf("addr(x)-addr(a) = %dn", system("PAUSE"); return 0; } b-a); a-b); (char*)y-a); y-(short*)a); (void*)&x-(void*)a); (double*)&x-(double*)a); Лекция 03. 10. 11 г. 11

Иллюстрация к вычитанию адресов int fact(int) 0 x 00401300 ? ? байт char a[256] Иллюстрация к вычитанию адресов int fact(int) 0 x 00401300 ? ? байт char a[256] 0 x 004050 DC short y[8] 0 x 004051 DC char b[513] 256 байт 256 байт 272 байта 16 байт 800 байт 0 x 004051 EC 528 байт int x 0 x 004053 FC 16 байт Лекция 03. 10. 11 г. 12

Указатели Адреса объектов программы можно сохранять в переменных специального вида – указателях. Указатели образуют Указатели Адреса объектов программы можно сохранять в переменных специального вида – указателях. Указатели образуют не один конкретный тип данных (хотя все указатели имеют один размер – 4 байта); типов указателей существует столько, сколько существует типов данных, т. е. для любого типа данных Т существует тип указателя Т*. Примеры типов указателей: char*, int*, short*, long*, float*, double*, void*, … Указатели типа void* называются нетипизированными указателями. При выполнении арифметических операций над такими указателями считается, что объекты, на которые они указывают, имеют размер 1 байт. … main() { int *px = &x; short *py = y; char *pa = a, *pb = void *pf = fact; printf("px = %pn", printf("py = %pn", printf("pa = %pn", printf("pb = %pn", printf("pf = %pn", system("PAUSE"); return 0; } b; px); py); pa); pb); pf); Лекция 03. 10. 11 г. 13

Доступ по указателю Ø В языке С существует одноместная операция * , применяемая к Доступ по указателю Ø В языке С существует одноместная операция * , применяемая к значениям, являющимся адресами (указателями). Результатом этой операции является объект программы, расположенный по данному адресу. Эта операция называется операцией ссылки по указателю, или операцией разыменования. Ø Операции & и * являются взаимно-дополняющими и, если они расположены подряд, взаимно компенсируют друга: Ø Пусть дано определение: int x; Тогда &x – адрес x; *&x – сама переменная x; &*&x – адрес x и т. д. Ø Переменная типа указатель может в разное время принимать различные значения. Следовательно, через одну и туже переменную-указатель можно обращаться к разным переменным (в разное время): int a=2, b=3, c=4; int* p=&a; *p=7; p=&b; *p=8; p=&c; *p=9; printf(“a=%d, b=%d, c=%dn”, a, b, c); //a=7, b=8, c=9 Лекция 03. 10. 11 г. 14

Указатели и аргументы функций В теории программирования известны два способа передачи аргументов в функцию: Указатели и аргументы функций В теории программирования известны два способа передачи аргументов в функцию: § передача по значению § передача имени (ссылке) В обоих случаях аргументы функции являются её локальными переменными и «не видны» извне. Различие способов состоит в том, что при вызове функции её аргументлокальная переменная получает : § в первом случае - копию значения фактического аргумента § во втором случае - ссылку на фактический аргумент (т. е. его адрес) и, следовательно, может им распоряжаться Каждый из этих способов имеет свои преимущества и недостатки и программист должен обоснованно подходить к выбору наиболее подходящего для конкретной ситуации способа. Наиболее известны два случая, когда необходимо использовать передачу по имени: § функция должна изменить значение передаваемого ей аргумента § размер аргумента велик и передача его по значению приведет к большим затратам памяти В языке С «штатно» реализован первый способ, однако и второй способ легко реализуется, если в качестве аргумента передавать адрес (указатель). Лекция 03. 10. 11 г. 15

Указатели и аргументы функций Рассмотрим еще раз функцию swap (обмен значениями) #include <stdio. h> Указатели и аргументы функций Рассмотрим еще раз функцию swap (обмен значениями) #include #include void swap(int *a, int *b) { int c = *a; *a = *b; *b = c; } int main() { int x = 5, y = 7; printf("x = %d, y = %dn", x, y); swap(&x, &y); printf("x = %d, y = %dn", x, y); system("PAUSE"); return 0; } Лекция 03. 10. 11 г. main x: 5 y: 7 swap a: &x b: &y 16

Указатели и аргументы функций Второй пример – передача массива в функцию по ссылке main Указатели и аргументы функций Второй пример – передача массива в функцию по ссылке main #include #include int sum(int m[], int n) { int i, s; for(i=0, s=0; i

Указатели и массивы Массив - это одна из наиболее простых структур данных, представляющих собой Указатели и массивы Массив - это одна из наиболее простых структур данных, представляющих собой линейную последовательность фиксированного количества однотипных элементов, расположенных в памяти друг за другом непрерывно и допускающих прямой доступ к любому элементу по его номеру (индексу). Массив характеризуется именем, типом элементов и их количеством. Пример определения массива: int x[50] x: addr x[0] x[1] x[2] x[3] … … 0 1 2 3 … … x[49] 49 В языке С существует очень тесная связь между массивами и указателями, которая характеризуется простым утверждением: Имя массива – это константный указатель на его начало Из этого утверждения следуют важные соотношения (здесь Т – имя типа): x[i] эквивалентно *(x+i) T x[] эквивалентно T *x Лекция 03. 10. 11 г. 18

Определение массивов При работе с массивами имеется одно затруднение – размер массива (количество элементов) Определение массивов При работе с массивами имеется одно затруднение – размер массива (количество элементов) должен быть известен в момент выделения памяти массиву компилятором. Проблем не возникает в следующих случаях: Ø размер массива задается константным выражением Ø размер массива задается выражением, в которое входят константы и внешние по отношению к блоку, в котором производится определение массива, переменные (и эти переменные получили значения!) Ø размер массива не задается, а определяется списком инициализации … #define N 100 int n 1 = 10; int main() { int n = 20; int a[N], b[30], c[n 1]; int g[] = {1, 2, 3, 4}; { int d[n]; } } Лекция 03. 10. 11 г. 19

Динамические массивы В тех случаях, когда размер массива невозможно знать заранее (например, эта величина Динамические массивы В тех случаях, когда размер массива невозможно знать заранее (например, эта величина вводится человеком), можно использовать динамические массивы, память для которых выделяется динамически системной функцией void *calloc(size_t n, size_t r) где n – количество элементов массива, r – размер элемента в байтах. Функция возвращает нетипизированный указатель на начало массива. #include #include int sum(int m[], int n) { int i, s; for(i=0, s=0; i

Версия только с указателями В этой версии программы используются только указатели. #include <stdio. h> Версия только с указателями В этой версии программы используются только указатели. #include #include int sum(int *m, int n) { int i, s; for(i=0, s=0; i

Освобождение памяти В программировании встречается неприятная ситуация, которая называется «утечка памяти» . Эта ситуация Освобождение памяти В программировании встречается неприятная ситуация, которая называется «утечка памяти» . Эта ситуация может возникнуть при использовании динамического выделения памяти функцией calloc. Для устранения этой ситуации рекомендуется освобождать неиспользуемую память с помощью функции void free(void *p) где p – нетипизированный указатель, получивший значение в результате вызова calloc. … int sum(int m[], int n) { int i, s; for(i=0, s=0; i