Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Industrial programming >> Язык C

Указатели в 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?

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

Допустимы операции: +, -, ++, --, вычитание двух указателей. Работает в единицах размера типа.

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 (количество элементов между указателями)

Правила:

Указатели и массивы

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.

Правила безопасности

  1. Всегда проверяйте возврат malloc/realloc на NULL.
  2. Каждому malloc — свой free (владелец памяти).
  3. После free(ptr) — ptr становится висячим (dangling). Хорошо: ptr = NULL;.
  4. Двойное free — UB.
  5. Утечка памяти — потеря указателя на выделенный блок.

Пример: динамический массив (вектор)

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

Инструменты поиска ошибок

Язык C

  1. Переменные, константы и литералы в C: полное руководство
  2. Классы хранения в C: область видимости, время жизни и производительность
  3. Освоение указателей в C: практическое руководство от экспертов
  4. Понимание взаимосвязи между массивами и указателями в C
  5. Передача адресов и указателей в функции в C: практическое руководство
  6. Освоение структур и указателей в C: практическое руководство
  7. Ключевое слово global в Python: как и когда использовать
  8. Указатели в C++: концепции, примеры и практические применения
  9. Переменные, типы данных и константы в C — практическое руководство
  10. Указатели в C: полное руководство по типам, использованию и лучшим практикам