Перейти к содержанию
  • Лента
  • Популярные
  • Последние
  • Теги
  • Пользователи
  • Сотрудничество
Свернуть
Логотип бренда
Категории
  1. Промышленный форум
  2. Категории
  3. ПO для чпу
  4. Указатели в C и работа с памятью: Полное руководство

Указатели в C и работа с памятью: Полное руководство

Запланировано Прикреплена Закрыта Перенесена ПO для чпу
1 Сообщения 1 Постеры 13 Просмотры 1 Отслеживают
  • Сначала старые
  • Сначала новые
  • По количеству голосов
Ответить
  • Ответить, создав новую тему
Авторизуйтесь, чтобы ответить
Эта тема была удалена. Только пользователи с правом управления темами могут её видеть.
  • TimT Не в сети
    TimT Не в сети
    Tim
    написал отредактировано
    #1

    Введение: Почему это нужно?

    Указатели — это фундаментальный инструмент C, который дает прямой доступ к памяти компьютера. Это не просто синтаксическая особенность, а критическая необходимость для:

    • Динамического выделения памяти — создавать структуры данных размером, известным только во время выполнения (связные списки, деревья, графы)
    • Передачи данных по ссылке — изменять переменные внутри функций и возвращать несколько значений
    • Работы со строками и массивами — эффективной манипуляции текстом и данными
    • Системного программирования — взаимодействия с операционной системой на низком уровне
    • Оптимизации памяти — использования ровно столько памяти, сколько нужно в данный момент

    Без указателей вы ограничены фиксированными размерами данных, известными на этапе компиляции. С указателями ваша программа становится гибкой и мощной.


    Основные концепции памяти

    Адрес и содержимое

    Каждый байт оперативной памяти компьютера имеет свой адрес — уникальный номер. На типичной 64-битной системе адреса — это огромные числа (часто в шестнадцатеричном формате, например 0x7ffc35f4).

    ┌─────────────────────────────┐
    │ Адрес памяти  │  Содержимое │
    ├─────────────────────────────┤
    │  0x1000       │     42      │  ← переменная x
    │  0x1001       │     0       │
    │  0x1002       │     100     │  ← переменная y
    │  0x1003       │     0       │
    │  0x1004       │  0x1000    │  ← указатель ptr (хранит адрес x)
    │  0x1005       │     0       │
    └─────────────────────────────┘
    

    Важно: указатель — это просто переменная, которая хранит адрес другой переменной.


    Операторы: & и *

    Это два основных оператора для работы с указателями.

    Оператор & (адреса) — “дай мне адрес”

    Он получает адрес переменной:

    int x = 42;
    int *ptr = &x;  // ptr теперь содержит адрес переменной x
    

    Читайте это как: “ptr — это указатель на int, присвоить ему адрес переменной x”.

    Оператор * (разыменование) — “дай мне значение по этому адресу”

    Он получает значение, на которое указатель указывает:

    int x = 42;
    int *ptr = &x;
    printf("%d\n", *ptr);  // Выведет 42
    

    Читайте это как: “содержимое по адресу, на который указывает ptr”.

    Практический пример

    #include <stdio.h>
    
    int main() {
        int age = 25;           // Обычная переменная
        int *ptr_age = &age;    // Указатель на age
        
        printf("Значение age: %d\n", age);           // 25
        printf("Адрес age: %p\n", (void*)&age);      // 0x7ffc35f4 (зависит от системы)
        printf("Значение по указателю: %d\n", *ptr_age);  // 25
        printf("Адрес в указателе: %p\n", (void*)ptr_age);// 0x7ffc35f4
        
        // Изменение через указатель
        *ptr_age = 30;
        printf("age теперь: %d\n", age);             // 30
        
        return 0;
    }
    

    Вывод:

    Значение age: 25
    Адрес age: 0x7ffc35f4
    Значение по указателю: 25
    Адрес в указателе: 0x7ffc35f4
    age теперь: 30
    

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


    Объявление указателей

    int *ptr;           // Указатель на int
    double *ptr_double;// Указатель на double
    char *ptr_char;    // Указатель на char (часто используется для строк)
    int **ptr_ptr;     // Указатель на указатель на int
    

    Важно: int *ptr означает, что указатель специализирован на работу с int. Он будет знать, как правильно читать 4 байта целого числа из памяти.

    Инициализация

    Всегда инициализируйте указатели:

    int *ptr = NULL;        // NULL = не указывает ни на что
    int x = 42;
    ptr = &x;               // Теперь указывает на x
    
    // Или сразу:
    int *ptr2 = &x;
    

    Неинициализированный указатель содержит мусор (случайный адрес) — это очень опасно.


    Динамическое выделение памяти

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

    malloc() — выделение памяти

    int *arr = malloc(10 * sizeof(int));  // Выделить память для 10 целых чисел
    

    Что здесь происходит:

    • malloc() просит операционную систему выделить 40 байт памяти (10 × 4 байта на int)
    • Возвращает адрес этого блока памяти
    • Этот адрес присваивается ptr, и теперь мы можем с ним работать
    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        // Выделяем память для 5 целых чисел
        int *numbers = malloc(5 * sizeof(int));
        
        // Проверяем, успешно ли выделена память
        if (numbers == NULL) {
            printf("Ошибка: не удалось выделить память\n");
            return 1;
        }
        
        // Заполняем значения
        numbers[0] = 10;
        numbers[1] = 20;
        numbers[2] = 30;
        numbers[3] = 40;
        numbers[4] = 50;
        
        // Печатаем
        for (int i = 0; i < 5; i++) {
            printf("numbers[%d] = %d\n", i, numbers[i]);
        }
        
        // Освобождаем память
        free(numbers);
        numbers = NULL;  // Хороший стиль — обнулить после free
        
        return 0;
    }
    

    Важно: sizeof(int) важен для портативности. На разных системах int может быть 2, 4 или 8 байт. Используя sizeof(), код работает везде.

    calloc() — выделение и инициализация

    int *arr = calloc(10, sizeof(int));  // Выделить память ДЛЯ 10 int, инициализировать нулями
    

    calloc() отличается от malloc() тем, что гарантирует, что все байты будут нулями (очень полезно для структур).

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

    Указатели поддерживают простую арифметику:

    int *ptr = malloc(5 * sizeof(int));
    ptr[0] = 10;
    ptr[1] = 20;
    ptr[2] = 30;
    
    // Эти две строки эквивалентны:
    printf("%d\n", ptr[2]);      // 30
    printf("%d\n", *(ptr + 2));  // 30
    

    Когда вы делаете ptr + 2, это не добавляет 2 байта, а добавляет 2 элемента (8 байт для int). C автоматически масштабирует операции по размеру типа данных.

    ptr    →  [10 | 20 | 30 | ? | ?]
    ptr+1  →     [20 | 30 | ? | ?]
    ptr+2  →        [30 | ? | ?]
    

    free() — освобождение памяти

    КРИТИЧНО: каждый malloc(), calloc() или realloc() должен иметь соответствующий free().

    int *ptr = malloc(sizeof(int) * 100);
    // ... используем ptr ...
    free(ptr);      // Освобождаем память
    ptr = NULL;     // Обнуляем (избегаем использования после free)
    

    Если забыть free(), происходит утечка памяти — ваша программа будет постепенно съедать всю оперативную память системы.


    Указатели и функции

    Передача по ссылке (изменение переменной в функции)

    В C нет “передачи по ссылке” как в C++. Вместо этого используются указатели:

    void swap(int *a, int *b) {
        int temp = *a;  // Прочитать значение
        *a = *b;        // Изменить значение
        *b = temp;
    }
    
    int main() {
        int x = 5, y = 10;
        printf("До: x=%d, y=%d\n", x, y);
        
        swap(&x, &y);  // Передаем адреса
        
        printf("После: x=%d, y=%d\n", x, y);
        return 0;
    }
    

    Вывод:

    До: x=5, y=10
    После: x=10, y=5
    

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

    Возврат нескольких значений

    void get_min_max(int arr[], int size, int *min, int *max) {
        *min = arr[0];
        *max = arr[0];
        
        for (int i = 1; i < size; i++) {
            if (arr[i] < *min) *min = arr[i];
            if (arr[i] > *max) *max = arr[i];
        }
    }
    
    int main() {
        int data[] = {3, 7, 2, 9, 1, 5};
        int min_val, max_val;
        
        get_min_max(data, 6, &min_val, &max_val);
        
        printf("Min: %d, Max: %d\n", min_val, max_val);  // Min: 1, Max: 9
        return 0;
    }
    

    Строки в C (массивы char)

    В C строки — это просто массивы символов, заканчивающиеся нулевым байтом (\0😞

    char *str = "Hello";  // Строка в памяти: H e l l o \0
    

    Выделение памяти для строки:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    int main() {
        // Выделяем память для строки длины 20 + 1 (для \0)
        char *name = malloc(21 * sizeof(char));
        
        // Копируем строку
        strcpy(name, "Alexander");
        
        printf("Имя: %s\n", name);
        printf("Длина: %lu\n", strlen(name));
        
        free(name);
        return 0;
    }
    

    Опасность: strcpy() может переполнить буфер. Используйте strncpy() или функции из <string.h>:

    strncpy(name, "Alexander", 20);  // Максимум 20 символов
    

    Указатель на указатель

    Указатель может указывать на другой указатель:

    int x = 42;
    int *ptr1 = &x;       // Указатель на x
    int **ptr2 = &ptr1;   // Указатель на указатель на x
    
    printf("%d\n", **ptr2);  // Выведет 42 (разыменовать дважды)
    

    Диаграмма:

            x: 42
            ↑
            |
          ptr1 → адрес x
          ↑
          |
        ptr2 → адрес ptr1
    

    Это редко нужно, но критично для:

    • Массивов указателей
    • Двумерных массивов, выделенных динамически
    • Сложных структур данных

    Структуры данных: связный список

    Полный практический пример — базовый связный список:

    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct Node {
        int data;
        struct Node *next;  // Указатель на следующий элемент
    } Node;
    
    // Добавить элемент в начало
    Node* insert_front(Node *head, int value) {
        Node *new_node = malloc(sizeof(Node));
        new_node->data = value;
        new_node->next = head;
        return new_node;
    }
    
    // Печать списка
    void print_list(Node *head) {
        Node *current = head;
        while (current != NULL) {
            printf("%d -> ", current->data);
            current = current->next;
        }
        printf("NULL\n");
    }
    
    // Освобождение памяти
    void free_list(Node *head) {
        Node *current = head;
        while (current != NULL) {
            Node *temp = current;
            current = current->next;
            free(temp);
        }
    }
    
    int main() {
        Node *list = NULL;
        
        // Добавляем элементы
        list = insert_front(list, 30);
        list = insert_front(list, 20);
        list = insert_front(list, 10);
        
        print_list(list);      // 10 -> 20 -> 30 -> NULL
        
        free_list(list);
        return 0;
    }
    

    Важно: каждый malloc() внутри insert_front() должен быть освобожден в free_list().


    Частые ошибки и как их избежать

    Ошибка 1: использование неинициализированного указателя

    int *ptr;     // Мусор в памяти!
    *ptr = 42;    // КРАХ — пишем в случайное место памяти
    

    Исправление:

    int *ptr = NULL;
    // или
    int *ptr = malloc(sizeof(int));
    

    Ошибка 2: утечка памяти

    for (int i = 0; i < 1000000; i++) {
        int *arr = malloc(1000);
        // Забыли free
    }  // Программа съедает 1 ГБ памяти!
    

    Исправление: всегда освобождайте память перед возвратом из функции или выходом из цикла.

    Ошибка 3: освобождение дважды

    int *ptr = malloc(sizeof(int));
    free(ptr);
    free(ptr);  // КРАХ — ptr уже не действителен
    

    Исправление:

    free(ptr);
    ptr = NULL;
    

    Ошибка 4: освобождение стекового указателя

    void bad_function(int **ptr) {
        int x = 42;
        *ptr = &x;  // Указываем на локальную переменную
    }  // x уничтожена, но ptr все еще указывает на неё!
    
    int main() {
        int *ptr = NULL;
        bad_function(&ptr);
        printf("%d\n", *ptr);  // Мусор или крах
    }
    

    Исправление: выделяйте динамическую память для долгоживущих структур.


    Лучшие практики

    1. Всегда инициализируйте: int *ptr = NULL;
    2. Проверяйте malloc: if (ptr == NULL) { /* обработать */ }
    3. Освобождайте память: каждый malloc → free
    4. Обнуляйте после free: free(ptr); ptr = NULL;
    5. Используйте sizeof: malloc(10 * sizeof(int)) вместо malloc(40)
    6. Документируйте правила: кто выделяет, кто освобождает?
    7. Используйте инструменты: Valgrind для поиска утечек памяти
    8. Ограничьте область: освобождайте в функции, где выделяли

    Проверка на утечки памяти (Valgrind)

    Если у вас Linux:

    gcc -g -o program program.c
    valgrind --leak-check=full ./program
    

    Valgrind покажет все утечки и кто их вызвал.


    Заключение

    Указатели в C — это суперспособность и огромная ответственность одновременно. Они позволяют:

    • Динамически выделять памяти
    • Строить сложные структуры (списки, деревья, графы)
    • Эффективно передавать и обрабатывать данные
    • Писать системный код

    Но требуют дисциплины: каждый malloc должен иметь free, каждый указатель должен быть инициализирован, каждый разыменование должно быть безопасным.

    Овладейте этим навыком — и половина сложности C отпадет. Вы сможете писать мощный, эффективный и красивый код.

    1 ответ Последний ответ
    1

    • kirilljsxK

      Работа с файлами в C: от открытия до закрытия

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу
      1
      0 Голоса
      1 Сообщения
      8 Просмотры
      Нет ответов
    • TimT

      Работа со строками в C: Полное руководство

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу
      1
      1 Голоса
      1 Сообщения
      9 Просмотры
      Нет ответов
    • kirilljsxK

      МЭК 61131-3: как программировать ПЛК по стандарту

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу мэк
      1
      2
      0 Голоса
      1 Сообщения
      13 Просмотры
      Нет ответов
    • locolizatorL

      EtherCAT и Modbus TCP: в чём разница и как выбрать для автоматизации

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу
      1
      1
      1 Голоса
      1 Сообщения
      8 Просмотры
      Нет ответов
    • kirilljsxK

      Бесплатные альтернативы дорогому CAM-ПО: FreeCAD, Carbide Create, Estlcam

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу чпу cnc
      1
      0 Голоса
      1 Сообщения
      11 Просмотры
      Нет ответов
    • kirilljsxK

      Как писать базовые УП для линейного перемещения и круговой интерполяции

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу gcode
      1
      0 Голоса
      1 Сообщения
      49 Просмотры
      Нет ответов
    • kirilljsxK

      Как сделать еврозапил на чпу станке

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу
      1
      3 Голоса
      1 Сообщения
      211 Просмотры
      Нет ответов
    • LizaL

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

      Отслеживается Игнорируется Запланировано Прикреплена Закрыта Перенесена ПO для чпу
      1
      1 Голоса
      1 Сообщения
      132 Просмотры
      Нет ответов

    Категории

    • Главная
    • Новости
    • Объявления
    • ПО и ЧПУ
    • Обсуждение

    Контакты

    • Сотрудничество
    • forum@investsteel.ru
    • Наш чат
    • Наш ТГ канал

    © 2022 - 2025 InvestSteel, Inc. Все права защищены.

    Политика конфиденциальности
    • Войти

    • Нет учётной записи? Зарегистрироваться

    • Войдите или зарегистрируйтесь для поиска.
    • Первое сообщение
      Последнее сообщение
    0
    • Лента
    • Популярные
    • Последние
    • Теги
    • Пользователи
    • Сотрудничество