*Указатели в языке Си - Часть 2

HexElf

Главный модератор
В первой части мы познакомились с основами указателей в Си — переменными, которые хранят адреса других переменных. Теперь рассмотрим более сложные концепции, связанные с указателями.

1. Нулевой указатель. Используется для инициализации указателей, которые будут назначены позже, или для проверки, был ли указатель инициализирован.
Пример:

#include <stdio.h>

int main(void) {
int *p = NULL;

if (p == NULL) {
printf("0\n");
}

return 0;
}


Ответ:

0

P.s printf("%d\n", *p) так делать нельза, будет ошибка сегментации

GDB:

Program received signal SIGSEGV, Segmentation fault.
0x000055555555514d in main ()


2. Массивы и указатели. В Си массивы тесно связаны с указателями. По сути, имя массива — это указатель на его первый элемент ( printf("Четвертый элемент: %d\n", p[3]); // 40.)

Пример:


#include <stdio.h>

int main(void) {
0 1 2 3 4
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;

printf("Первый элемент: %d\n", *p); //10
printf("Второй элемент: %d\n", *(p + 1)); // 20
printf("Третий элемент: %d\n", *(p + 2)); // 30

printf("Четвертый элемент: %d\n", p[3]); // 40

return 0;
}


Ответ:
Первый элемент: 10
Второй элемент: 20
Третий элемент: 30
Четвертый элемент: 40


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

3. Константные указатели.

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

Пример:


#include <stdio.h>

int main(void) {
int x = 10, y = 20;

// Указатель на константу
const int *p1 = &x;
p1 = &y; //Можно изменить адрес

// Константный указатель
int * const p2 = &x;
*p2 = 30; // Можно изменить значение


// Константный указатель на константу
const int * const p3 = &x;

printf("%d\n", x);

return 0;
}


Как избежать утечек памяти и "висячих" указателей

1. Утечки памяти. Происходит, когда программа выделяет память, но не освобождает ее.

Пример:

Неправильная программа.


#include <stdio.h>
#include <stdlib.h>

void memory_leak_example() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
printf("Значение: %d\n", *p);
}

int main(void) {
memory_leak_example();

return 0;
}


Valgrind:

==134480== HEAP SUMMARY:
==134480== in use at exit: 4 bytes in 1 blocks
==134480== total heap usage: 2 allocs, 1 frees, 1,028 bytes allocated
==134480==
==134480== 4 bytes in 1 blocks are definitely lost in loss record 1 of 1
==134480== at 0x48457A8: malloc (vg_replace_malloc.c:446)
==134480== by 0x10915A: memory_leak_example
==134480== by 0x109195: main
==134480== LEAK SUMMARY:
==134480== definitely lost: 4 bytes in 1 blocks
==134480== indirectly lost: 0 bytes in 0 blocks
==134480== possibly lost: 0 bytes in 0 blocks
==134480== still reachable: 0 bytes in 0 blocks
==134480== suppressed: 0 bytes in 0 blocks


В общем сообщаеться что 4 байта не были освобождены.

Правильная программа.

#include <stdio.h>
#include <stdlib.h>

void proper_memory_usage() {
int *p = (int *)malloc(sizeof(int));
if (p == NULL) {
printf("Ошибка выделения памяти\n");
return;
}
*p = 10;
printf("Значение: %d\n", *p);
free(p);
p = NULL; \\Обнулять указатель после освобождения
}

int main(void) {
proper_memory_usage();
return 0;
}


Valgrind:

HEAP SUMMARY:
==148324== in use at exit: 0 bytes in 0 blocks
==148324== total heap usage: 2 allocs, 2 frees, 1,028 bytes allocated
==148324== All heap blocks were freed -- no leaks are possible


Что нового? В первой программе мы забыли вызвать free(p) из-за этого утечка. Во второй мы выделяем память, сначала с помощью sizeof(int) определяем размер памяти, который нужно выделить, malloc(sizeof(int)) - выделяем блок памяти указанного размера. Обязательна проверка, правильно ли выделили память.

2. "Висячий" указатель. Это указатель, который ссылается на объект, который был удален или перемещен.

Пример:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
int *p1 = (int *)malloc(sizeof(int));
*p1 = 10;

int *p2 = p1;

free(p1);
p1 = NULL;

return 0;
}


Что делать не нужно, к примеру, printf("%d\n", *p2); — мы уже освободили память, но вызвав printf, мы продолжаем с ним работать, что и вызывает "висячий" указатель.

Valgrind:

==171731== Invalid read of size 4
==171731== at 0x109199: main
==171731== Address 0x4a86040 is 0 bytes inside a block of size 4 free'd
==171731== at 0x48488EF: free
==171731== by 0x10918C: main
==171731== Block was alloc'd at
==171731== at 0x48457A8: malloc
==171731== by 0x10916A: main


Возможно, выход третьей части, ибо недавно для себя открыл новые возможности указателей, их тьма тьмущая:happygirl
 
Отредактировано автором:
Сверху