Указатели в C: полное руководство — объявление, арифметика, массивы, функции и динамическая память
Что такое указатель?
Указатель (pointer) — переменная, хранящая адрес другой переменной в памяти. Позволяет косвенный доступ к данным, работу с динамической памятью, эффективную передачу больших структур в функции и построение сложных структур данных (списки, деревья, графы).
Объявление и базовые операции
int var = 42;
int *ptr; // объявление указателя на int
ptr = &var; // взятие адреса (&) — ptr теперь указывает на var
int val = *ptr; // разыменование (*) — чтение значения по адресу
*ptr = 100; // запись через указатель (var становится 100)
Синтаксис: тип *имя;. Звёздочка привязывается к имени: int *p, q; — p указатель, q — int. Лучше: int *p; int q;.
NULL-указатель
int *ptr = NULL; // или 0, или (void*)0
if (ptr) { ... } // проверка: не NULL?
if (!ptr) { ... } // проверка: NULL?
NULLопределен в<stddef.h>,<stdio.h>,<stdlib.h>.- Разыменование
NULL→ UB (обычно сегфолт). - Всегда инициализируйте указатели (
NULLили валидный адрес).
Арифметика указателей
Допустимы операции: +, -, ++, --, вычитание двух указателей. Работает в единицах размера типа.
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // &arr[0]
p++; // p теперь указывает на arr[1] (адрес + sizeof(int))
p += 2; // на arr[3]
int diff = p - arr; // 3 (количество элементов между указателями)
Правила:
- Указатель + целое = указатель.
- Указатель - целое = указатель.
- Указатель - указатель =
ptrdiff_t(разница в элементах). - Сравнение указателей (
==,<) — только в пределах одного массива/объекта. - Выход за границы массива (кроме «один за концом») — UB.
Указатели и массивы
arr[i] тождественно *(arr + i) и *(i + arr) и даже i[arr] (редко используют).
int arr[5] = {1,2,3,4,5};
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // или p[i]
}
Указатели на указатели (многоуровневые)
int val = 100;
int *p1 = &val;
int **p2 = &p1;
int ***p3 = &p2;
**p2 = 200; // val = 200
***p3 = 300; // val = 300
Применение: динамические 2D массивы, изменение указателя в функции, argv в main.
Указатели и функции
Передача по ссылке (изменение аргумента)
void swap(int *a, int *b) {
int tmp = *a;
*a = *b;
*b = tmp;
}
swap(&x, &y);
Возврат указателя
// ОПАСНО: возврат указателя на локальную переменную!
int *bad() {
int local = 42;
return &local; // UB после выхода из функции!
}
// ПРАВИЛЬНО: возврат динамической памяти
int *create_array(size_t n) {
int *arr = malloc(n * sizeof(int));
return arr; // вызывающий ОБЯЗАН free()
}
// ПРАВИЛЬНО: возврат указателя на static
int *get_static_buffer() {
static int buf[100];
return buf;
}
Указатели на функции
int add(int a, int b) { return a + b; }
int (*op)(int, int) = add; // объявление
op(3, 4); // 7
// Колбэки
void sort_with_callback(int *arr, size_t n, int (*cmp)(int, int)) {
// использует cmp для сравнения
}
Динамическое выделение памяти
Функции из <stdlib.h>:
| Функция | Описание |
|---|---|
malloc(size) | Выделяет size байт, не инициализирует. Возвращает void* или NULL. |
calloc(n, size) | Выделяет массив n элементов по size байт, обнуляет. |
realloc(ptr, new_size) | Изменяет размер блока. Может переместить память. Старый ptr невалиден после успеха. |
free(ptr) | Освобождает память. ptr должен быть от malloc/calloc/realloc или NULL. |
Правила безопасности
- Всегда проверяйте возврат
malloc/reallocнаNULL. - Каждому
malloc— свойfree(владелец памяти). - После
free(ptr)—ptrстановится висячим (dangling). Хорошо:ptr = NULL;. - Двойное
free— UB. - Утечка памяти — потеря указателя на выделенный блок.
Пример: динамический массив (вектор)
typedef struct {
int *data;
size_t size;
size_t capacity;
} IntVec;
void vec_init(IntVec *v) {
v->data = NULL;
v->size = v->capacity = 0;
}
void vec_push(IntVec *v, int val) {
if (v->size == v->capacity) {
size_t new_cap = v->capacity ? v->capacity * 2 : 4;
int *new_data = realloc(v->data, new_cap * sizeof(int));
if (!new_data) { /* handle error */ return; }
v->data = new_data;
v->capacity = new_cap;
}
v->data[v->size++] = val;
}
void vec_free(IntVec *v) {
free(v->data);
v->data = NULL;
v->size = v->capacity = 0;
}
Указатели на void (generic pointers)
void *ptr = malloc(100); // malloc возвращает void*
int *ip = ptr; // неявное приведение void* → int* (в C, не в C++)
free(ptr); // free принимает void*
Используются для универсальных функций (memcpy, qsort, контейнеры).
Квалификаторы к указателям
int value = 10;
const int *p1 = &value; // указатель НА const int (значение нельзя менять через p1)
int *const p2 = &value; // const указатель на int (указатель нельзя менять)
const int *const p3 = &value; // и то, и другое
Правило чтения справа налево: const int *p → p is a pointer to const int.
Ограничение указателей (C99: restrict)
void copy(int *restrict dst, const int *restrict src, size_t n) {
// компилятор знает: dst и src не перекрываются (no aliasing)
// может векторизовать цикл
}
Частые ошибки и UB
- Разыменование неинициализированного/NULL/висячего указателя.
- Выход за границы массива через указатель.
- Возврат указателя на локальную переменную.
- Приведение указателей несовместимых типов (strict aliasing violation).
- Использование после
free(use-after-free). - Утечки памяти (нет
free).
Инструменты поиска ошибок
- Valgrind (memcheck) — утечки, невалидные доступы, use-after-free.
- AddressSanitizer (ASan) —
-fsanitize=address -g(GCC/Clang). Быстрый, ловит переполнения буфера, use-after-free, double-free. - Статический анализ: Cppcheck, Clang Static Analyzer, Coverity.
Язык C
- Переменные, константы и литералы в C: полное руководство
- Классы хранения в C: область видимости, время жизни и производительность
- Освоение указателей в C: практическое руководство от экспертов
- Понимание взаимосвязи между массивами и указателями в C
- Передача адресов и указателей в функции в C: практическое руководство
- Освоение структур и указателей в C: практическое руководство
- Ключевое слово global в Python: как и когда использовать
- Указатели в C++: концепции, примеры и практические применения
- Переменные, типы данных и константы в C — практическое руководство
- Указатели в C: полное руководство по типам, использованию и лучшим практикам