В C массивы и указатели неразрывно связаны. Вы не сможете писать эффективный код, не понимая их взаимосвязи. На освоение этого уходит время. Но награда — мгновенный контроль над памятью — приходит сразу.
Рассмотрим базовый пример. Вы объявляете два целочисленных массива размером десять.
Попробуйте скомпилировать этот код. Он не скомпилируется. Вы не можете присвоить один массив другому с помощью знака равенства. C не поддерживает копирование блоков данных через операторы присваивания. Чтобы скопировать a в b, вам нужно пройти циклом по каждому элементу.
Вы могли бы сжать этот цикл в одну строку, используя запятую, но это ухудшит читаемость. Стандартный подход — использовать функцию memcpy из заголовочного файла string.h. Она эффективно выполняет побайтовое копирование.
Неудача присваивания b=a раскрывает фундаментальное правило. В C переменные массива — это не сами массивы. Это фиксированные указатели. a и b — это постоянные идентификаторы, указывающие на начальный адрес своих блоков памяти. a указывает на a[0]. b указывает на b[0]. Эти адреса неизменяемы. Вы не можете перенаправить имя переменной на другое место в памяти. Именно поэтому присваивание завершается ошибкой. Компилятор рассматривает имя массива как константный указатель.
Поскольку имя по сути является указателем, вы можете использовать арифметику указателей для доступа к данным. Здесь синтаксис становится мощным инструментом.
Здесь p — это переменная-указатель. Вы можете присвоить адрес массива a переменной p. Выражение p=a копирует адрес. Теперь p содержит то же значение, что и имя массива a. Разыменование p с помощью *p возвращает первый элемент. Вывод будет равен нулю.
Это различие важно, потому что переменные-указатели можно изменять. Вы можете заставить p указывать на a[1] или a[5], используя арифметику. Само имя массива остаётся закреплённым в начале. Эта гибкость позволяет функциям перебирать память без копирования больших структур данных. Это основа производительности на низком уровне в C.
Понимание этой связи предотвращает тонкие ошибки. Она объясняет, почему некоторые функции принимают указатели вместо синтаксиса массивов. Она проясняет структуру памяти. Пренебрегите этим знанием, и вы будете бороться с ошибками сегментации или непреднамеренными перезаписями данных. Освойте его, и вы будете точно видеть, что делает машина.
Синтаксис может сначала показаться непривычным. Двойное значение квадратных скобок и звёздочек сбивает с толку новичков. Но когда модель становится понятной, абстракция исчезает. Остаются только адреса и значения. Ничего больше.
Стоит ли этих усилий? Код становится быстрее. Расход памяти снижается. Вы получаете прямой доступ к операциям на уровне оборудования. Большинство программ на C полагаются
Вы можете напрямую присвоить переменную-массив указателю, потому что «под капотом» они по сути являются одним и тем же.
p=a; работает не благодаря магии, а потому что a распадается до указателя. А именно, он указывает на адрес памяти первого элемента массива (с индексом 0). Поскольку этот элемент является целым числом, a действует как указатель на одно целое число (int). Объявление p как указателя на целое число и присваивание ему значения a — это просто предоставление двум переменным общего адреса. Вместо этого вы могли бы написать p=&a[0];. Результат будет идентичным.
Как только p связывается с началом a, вы получаете возможность перемещаться. Имена массивов, такие как a, являются константами; это фиксированные адреса, которые нельзя изменить. Указатели, такие как p, ведут себя иначе. Вы можете перемещать их, используя арифметику указателей.
Здесь C проявляет свою изобретательность.
Когда вы пишете p++;, компилятор не просто прибавляет один байт к адресу. Он знает, что p указывает на целое число. Вместо этого он прибавляет sizeof(int) байт, попадая точно на следующий элемент. Если бы p указывал на структуру длиной 100 байт, p++ переместил бы указатель на 100 байт вперед. C выполняет всю математику за вас. Вы просто перемещаете указатель.
Копирование массивов без индексов
Вы можете скопировать один массив в другой, используя эти указатели. Вместо стандартного цикла с индексами:
Вы можете использовать указатели:
Это многословно. Это можно сжать.
Или сжать всё в одну строку.
Здесь операторы выполняют тяжелую работу. Оператор * имеет более высокий приоритет, чем ++, но постинкремент происходит после получения значения. Таким образом, *p++ получает текущее значение, а затем перемещает указатель. *q++ делает то же самое для места назначения. Присваивание происходит между этими действиями. Это компактно. Это быстро. Но это также легко неправильно прочитать, если не быть внимательным.
Опасная зона
Что произойдет, если вы выведете p или q за пределы массива?
C не остановит вас.
Компилятор предполагает, что вы знаете, что делаете. Он будет продолжать увеличивать указатели, копируя данные в память, которая не принадлежит массиву. Вы можете перезаписать другие переменные. Вы можете испортить стек. Вы можете вызвать сбой программы позже, в месте, не связанном с ошибкой.
Именно поэтому проверка границ в C выполняется вручную. Язык доверяет вам. Когда вы выходите за рамки правил, вы платите за это цену.
Передача массивов в функции
Вы можете передавать массивы в функции двумя способами. Они выглядят по-разному, но ведут себя аналогично.
Рассмотрим функцию, которая выводит массив:
Или версия с указателями:
Параметр nia (количество элементов в массиве) обязателен. Массивы в C не хранят свой размер вместе с собой. Функции нужно знать, когда остановиться.
В обоих случаях вы передаете указатель на начало массива, а не







































