Операторы разыменования * и взятия адреса & — одни из фундаментальных инструментов работы с памятью в C. Они позволяют прямое управление данными в памяти, что сделало C популярным языком для системного программирования.
История вопроса:
С момента появления языка C (в 1970-е) его философия была тесно связана с низкоуровневым управлением памятью. Операторы * и & реализуют технику косвенной адресации, применяемую на уровне процессора, что позволяет работать с указателями, динамически выделять память, создавать эффективные структуры данных.
Проблема: Ошибки в использовании этих операторов приводят к многочисленным багам: утечкам памяти, повреждению данных, сегфолтам. Компилятор не всегда явно сигнализирует об этих ошибках, особенно если типы указателей совпадают по размеру, но отличаются по содержимому.
Решение: Внимательно относиться к типу указателя, отслеживать жизненный цикл выделяемой памяти, проводить инициализацию и корректное освобождение, а также проверять корректность операций разыменования и используемых адресов.
Пример кода:
int x = 10; int *p = &x; // взятие адреса int y = *p; // разыменование (получаем значение по адресу) // Работа с указателем на массив int arr[3] = {1,2,3}; int *pa = arr; printf("%d", *(pa+1)); // второй элемент массива
Ключевые особенности:
Можно ли взять адрес временной переменной, например: & (x + y)?
Нет, адрес выражения взять нельзя, потому что результат выражения — не объект памяти. Адрес можно брать только от переменной, массива или структуры.
Пример кода:
int z = 5; int p = &(z + 1); // Ошибка компиляции
Чем отличается разыменование указателя на void?
Указатель типа void * нельзя разыменовать напрямую, пока не приведёшь его к конкретному типу. Это универсальный указатель, но операции разыменования типонезависимы только после явного приведения:
void *pv = &x; int value = *(int*)pv; // ОК
Можно ли разыменовать нулевой указатель (NULL)?
Нет, это приводит к неопределённому поведению — разрушение памяти или аварийное завершение. Всегда проверяйте указатель перед разыменованием:
int *ptr = NULL; if (ptr) { *ptr = 10; // Никогда не выполнится }
Разработчик берёт адрес локальной переменной в функции, возвращает его, а затем разыменовывает указатель в вызывающем коде.
Плюсы:
Минусы:
Используется динамическое выделение памяти под переменную, адрес возвращается вызывающему коду и в конце освобождается через free.
Плюсы:
Минусы: