Структура, переданная в функцию, копируется. Для точки из двух int это нормально, а для записи в 200 байт расточительно — и делает изменение невозможным, потому что функция видит только собственную копию. Указатель на структуру решает обе проблемы разом, а ещё это единственный способ строить растущие структуры данных: списки, деревья, графы.
Как взять указатель на структуру
Ничего особенного не требуется. & даёт адрес, а тип получается «указатель на структуру»:
Все три печатают 3. Последние две — одна и та же операция, записанная двумя способами.
Почему в (*ptr).x нужны скобки
Может возникнуть желание их опустить и написать *ptr.x. Это компилируется во что-то совсем другое, а чаще вообще отказывается компилироваться, потому что оператор доступа к полю . имеет более высокий приоритет, чем разыменование *:
*ptr.x // разбирается как *(ptr.x) -- неверно
(*ptr).x // сначала разыменовать, потом взять поле -- верно
ptr->x // то же самое, только короче
ptr.x просит поле у указателя, а у указателя полей нет. Жалоба компилятора («request for member 'x' in something not a structure or union») — это переодетая ошибка приоритетов.
Поскольку форма со скобками уродлива и в ней легко ошибиться, C предоставляет ->. На практике (*p).x в реальном коде вы почти не увидите: берите p->x и забудьте о длинной форме — разве что как об объяснении того, что означает стрелка.
Передача структуры по указателю
Параметр-указатель даёт функции настоящую структуру вызывающего, поэтому изменения сохраняются:
Две привычки, которые стоит здесь выработать:
- Помечайте указатели только для чтения как
const.const struct Point *pобещает, что функция не изменит то, на что указываетp. Если кто-то позже добавит внутрьp->x = 0, сборка сломается вместо того, чтобы баг уехал в релиз. Это ещё и документирует намерение в месте вызова. - Передавайте указатель для больших структур даже при чтении. Структура с буфером имени на 64 байта стоит 64 байта копирования на каждый вызов по значению; указатель стоит 8.
Небольшие структуры по-прежнему нормально передавать по значению: struct Point в 8 байт копируется так же дёшево, как указатель, а версия по значению не может быть NULL, что убирает целый класс отказов.
Указатели внутри массивов структур
Арифметика указателей на массивах структур работает ровно так же, как на массивах int: p + 1 продвигается на одну целую структуру, включая байты выравнивания.
Имя массива превращается в указатель на первый элемент, поэтому при вызове staff уже является struct Employee *. По той же причине функции нужно отдельно передавать n: sizeof внутри функции измерил бы указатель, а не массив.
Размещение структуры в куче
Структуры на стеке умирают в конце своей области видимости. Чтобы создать такую, что переживёт создавшую её функцию, — или чтобы создать столько, сколько потребует ввод, — выделяйте память через malloc:
Четыре детали в этой маленькой функции — все несущие:
sizeof *e, а неsizeof(struct Employee). Это читается как «размер того, на что указываетe», поэтому при смене типа выделение подстроится само. Разойтись этим двум вещам просто негде.- Проверка на
NULL.mallocвозвращаетNULL, когда не может удовлетворить запрос. Записьe->idчерез нулевой указатель — это ошибка сегментации. - Инициализация каждого поля.
mallocне обнуляет память; структура приходит забитой мусором. (calloc(1, sizeof *e)обнулит её за вас.) - Кто-то обязан вызвать
free. Возврат выделенного указателя перекладывает эту обязанность на вызывающего. Скажите об этом в комментарии: неясное правило владения — это то, с чего начинаются утечки памяти.
Ради чего всё: узел связного списка
Вот то, чего структуры не могут без указателей. Структура не может содержать саму себя — она была бы бесконечно большой, — но может содержать указатель на структуру своего же типа, и этот единственный приём строит все связные структуры данных в C.
Обратите внимание, что struct Node *next; ссылается на struct Node в тот момент, когда struct Node ещё объявляется. Это законно именно потому, что у указателя известный размер независимо от того, на что он указывает: полное определение компилятору пока не нужно. Это же единственное место, где приходится использовать тег struct Node, даже если вы написали и typedef: имя из typedef внутри собственного определения ещё не существует.
Цикл free_list сохраняет head->next до вызова free(head). Чтение head->next после освобождения — это использование освобождённой памяти: она больше не ваша, и прочитанное значение будет тем, что положил туда аллокатор.
Частые ошибки
p->xна неинициализированном илиNULLуказателе. Самая частая причина падения в коде, насыщенном структурами. Инициализируйте указатели значениемNULLи проверяйте перед разыменованием.- Возврат указателя на локальную структуру.
struct Point *bad(void) { struct Point p = {1,2}; return &p; }возвращает адрес памяти, которая перестаёт существовать в момент возврата. Возвращайте структуру по значению или выделяйте её в куче. - Двойное освобождение или забытый free. Каждому
mallocсоответствует ровно одинfree. После освобождения присвойте указателюNULL, чтобы позднее случайное использование громко упало, а не тихо испортило память. - Копирование структуры, содержащей указатель. Присваивание структуры копирует значение указателя, а не то, на что он указывает. Теперь обе структуры ссылаются на один буфер, и та, что освободит его первой, оставит другую с висячим указателем.
Часто задаваемые вопросы
Что делает оператор -> в C?
-> в C?p->x обращается к полю x через указатель p. Это в точности эквивалентно (*p).x: разыменовать указатель, затем взять поле. Стрелка существует потому, что указатели на структуры встречаются так часто, что длинная форма загромождает код.
Почему *p.x не работает для указателя на структуру?
*p.x не работает для указателя на структуру?Потому что . связывает сильнее, чем *. *p.x разбирается как *(p.x): взять поле x у p (а это указатель, а не структура) и разыменовать его. Нужно писать (*p).x или просто p->x.
Передавать структуру по значению или по указателю в C?
По указателю — когда функция должна изменить структуру вызывающего или когда структура велика и копировать её расточительно. По значению — для небольших структур только для чтения, где копия дёшева, а код проще. Помечайте параметры-указатели только для чтения как const struct T *, чтобы обещание проверял компилятор.
Как выделить структуру через malloc в C?
struct Node *n = malloc(sizeof *n); — форма sizeof *n спрашивает размер того, на что указывает n, поэтому она останется верной, если тип позже изменится. Проверьте результат на NULL, инициализируйте каждое поле и вызовите free(n), когда закончите.