Добавил:
Рад, если кому-то помог Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
0
Добавлен:
01.11.2025
Размер:
1.27 Mб
Скачать

МИНИСТЕРСТВО ЦИФРОВОГО РАЗВИТИЯ, СВЯЗИ И МАССОВЫХ КОММУНИКАЦИЙ РОССИЙСКОЙ ФЕДЕРАЦИИ

Федеральное государственное бюджетное образовательное учреждение

высшего образования

«Поволжский государственный университет телекоммуникаций и информатики»

Факультет

Факультет № 1.

Направление подготовки / специальность

09.03.01 Информатика и вычислительная техника

Кафедра информатики и вычислительной техники

ФИО студента

Группа студента

,Лабораторная работа № 3

по программированию С

Тема: Указатели

Цель работы: познакомиться с адресацией памяти, научиться правильно использовать указатели различных типов.

Ход работы:

1.

Анализ результатов:

  1. Инициализация переменных и указателей:

    • Переменные a, b, c размещаются в памяти со своими значениями

    • Указатели p1, p2, p3 содержат адреса этих переменных

    • Размеры переменных: int - 4 байта, float - 4 байта, double - 8 байтов

    • Размеры указателей одинаковы (обычно 4 или 8 байтов в зависимости от архитектуры)

  2. Изменение значений через указатели:

    • *p1 = 5 изменяет значение a на 5

    • *p2 = *p2 * (*p1) умножает b на 5 (2 × 5 = 10)

    • *p3 = sqrt(*p3) вычисляет квадратный корень из 3 (≈1.732)

  3. Приведение типов указателей:

    • p1 = (int*)p2 - указатель на int теперь указывает на float

    • При разыменовании *p1 интерпретирует байты float как int (некорректное значение)

    • p4 = p2 - void* может хранить любой адрес, но для доступа нужно явное приведение

  4. Арифметика указателей:

    • p1++ увеличивает адрес на sizeof(int) = 4 байта

    • p3-- уменьшает адрес на sizeof(double) = 8 байтов

    • После этих операций указатели указывают на неинициализированную память

Дамп памяти:

Адрес: 0x1000: a = 1 (4 байта)

0x1004: b = 2.0 (4 байта)

0x1008: c = 3.0 (8 байтов)

0x1010: p1 = 0x1000 (указатель)

0x1018: p2 = 0x1004 (указатель)

0x1020: p3 = 0x1008 (указатель)

2.

Список исправленных ошибок:

  1. scan("%d", a); → scanf("%d", a); - неверное имя функции

  2. Несоответствия типов указателей (не добавлены явные приведения, но отмечены как ошибки)

  3. y = 12345,6789; → y = 12345.6789; - запятая вместо точки в вещественном числе

  4. printf("m=%p\t*m=%lf\tm=%lf\n", m, *m, n); - повтор m=%lf вместо n=%lf

  5. Несоответствия спецификаторов формата в printf

  6. n=%p → &n=%p в выводе адреса переменной n

  7. Смешение типов в арифметике без явных приведений

Анализ результатов после замены m+=2 на m++:

  • m += 2 сдвигает указатель на 2 × sizeof(double) = 16 байтов

  • m++ сдвигает указатель на sizeof(double) = 8 байтов

  • В обоих случаях указатель выходит за границы переменной n

  • Операция *m обращается к неинициализированной памяти (неопределенное поведение)

  • Результат вычисления *m = (float)*a - n + (int)*x будет разным из-за разного смещения указателя

Анализ результатов:

  1. Работа с байтовыми операциями:

    • p = (char*)a позволяет работать с отдельными байтами целого числа

    • Обмен байтов *p = *(p+3) меняет порядок байтов в числе (endianness)

  2. Приведение типов указателей:

    • При присваивании a = (int*)x указатель на int указывает на float

    • *a = (int)*x приводит float к int (отбрасывает дробную часть)

  3. Арифметика указателей:

    • m++ увеличивает адрес на sizeof(double) = 8 байтов

    • Указатель m теперь указывает за пределы переменной n

    • Операция *m обращается к неинициализированной памяти

  4. Размеры типов и выравнивание:

    • Разные типы имеют разные размеры в памяти

    • Неправильное приведение типов приводит к некорректной интерпретации данных

Дамп памяти:

Адрес: 0x2000: b = ? (4 байта)

0x2004: y = 3.5 (4 байта)

0x2008: n = ? (8 байтов)

0x2010: p = ? (указатель)

0x2018: c = ? (1 байт)

0x2020: a = ? (указатель)

0x2028: x = ? (указатель)

0x2030: m = ? (указатель)

Дамп памяти показывает, что неправильная арифметика указателей приводит к доступу за пределами выделенной памяти, что является серьезной ошибкой.

Ответы на контрольные вопросы

  1. Что такое указатель?

Указатель - это переменная, хранящая адрес в памяти, по которому находится другое значение.

  1. Какой объём памяти занимает указатель?

4 байта на 32-битной системе, 8 байт на 64-битной системе

  1. Что является значением переменной-указателя?

Адрес в памяти

  1. Как проинициализировать указатель?

Указатель можно инициализировать значением NULL или адресом переменной.

  1. Что такое NULL?

NULL - это специальное значение, указывающее, что указатель не указывает на действительное место в памяти.

  1. Что такое указатель на void? Зачем нужны такие указатели?

Указатель на void (void*) - это указатель, который не имеет типа данных. Он может хранить адрес любой переменной, но при этом к нему нельзя применить операцию разыменования. Такие указатели используются для:

  • Передачи данных неопределенного типа в функции.

  • Создание универсальных функций, работающих с разными типами данных.

  1. Какие операции допустимы при работе с указателями?

    • Инициализация: Присваивание значения NULL или адреса переменной.

    • Операция разыменования: Доступ к значению, расположенному по адресу, на который указывает указатель (*ptr).

    • Присваивание: Присваивание указателю адреса другой переменной.

    • Сравнение: Сравнение указателей на равенство или неравенство.

    • Арифметические операции: Инкремент (++), декремент (--), сложение (+), вычитание (-) применяются к указателям, но с учетом размера типа данных, на который они указывают.

    • Применение в циклах: Проход по массиву с помощью указателя.

  1. Чем отличается унарная операция "&" от унарной "*" ?

    • & (адрес-оператор) возвращает адрес переменной.

    • * (оператор разыменования) возвращает значение, расположенное по адресу, на который указывает указатель.

  1. Совместимость типов указателей.

Указатели на разные типы данных несовместимы. Нельзя присваивать адрес переменной одного типа указателю на другой тип без явного преобразования.

  1. Можно ли получить адрес указателя?

Да, можно получить адрес указателя с помощью оператора "&".

  1. Можно ли указателю присвоить его же адрес?

Да, можно. В этом случае указатель будет указывать на себя самого.

  1. Почему к указателю на void нельзя применить операцию разыменования?

Потому что компилятор не знает, какой тип данных находится по адресу, на который указывает указатель на void. Чтобы разыменовать указатель на void, необходимо привести его к нужному типу.

  1. Как работают операции инкремента и декремента, примененные к указателям?

Смещается на размер типа данных, на который он указывает.

  1. Каков результат операции вычитания, примененной к указателям одного типа?

Количество элементов между этими указателями.

  1. Какой спецификатор типа используется при выводе адреса на экран с помощью функции printf()?

Спецификатор типа %p.

  1. В чем отличие записи (double*) а от (double)*а, если а — указатель на целое число?

  • (double*) а - приводит указатель а к типу double*.

  • (double)*а - разыменовывает указатель а и приводит полученное значение к типу double.

  1. В чем отличие записи *а++ от (*а)++, если а — некоторый указатель, отличный от void*?

    • *а++ - сначала разыменовывает указатель а, а затем увеличивает сам указатель.

    • (*а)++ - сначала увеличивает значение, на которое указывает а, а затем разыменовывает указатель.

18. Как описать указатель на начало массива?

int arr[10];

int *ptr = arr; // или int *ptr = &arr[0];

19. Как описать указатель на указатель?

int *ptr1;

int **ptr2 = &ptr1; // ptr2 - указатель на указатель ptr1

20. Когда и зачем может повторно использоваться операция разыменования?

Повторное разыменование используется для доступа к значениям, хранящимся по адресу, на который указывает указатель на указатель.

Соседние файлы в папке Лаба3