Распространенные ошибки - C (СИ) - Ответ 6780567

12. scanf() - ввод текстовых строк

А в чем проблема?

Такой вопрос обычно возникает у людей, которые считают, что массив физически представлен неким указателем на некий блок памяти. Соответственно ожидается , что '&mas' даст нам указатель на этот указатель и не будет работать в 'scanf'. Поэтому работоспособность такого 'scanf' зачастую вызывает удивление.

На самом деле массивы в языке С не являются и никогда не являлись указателями. Это очень популярная легенда, которая никак не хочет умирать, несмотря на то, что материалов, объясняющих семантику С массивов написано очень много на всех языках.

Массив - это просто непрерывный блок памяти, содержащий элементы массива. Т.е. например 'int a[20]' - это блок памяти из 20 объектов типа 'int'. И все. Имя 'a', как объект - это объект типа 'int [20]', который соответствует именно и только этому блок памяти. Никакого указателя там нигде нет.

Иллюзия того, что массив "является указателем" возникает из-за того, что когда вы используете имя массива в выражениях, то

почти всегда

к массиву применяется

неявное преобразование типа

, которое автоматически "на лету" преобразует объект типа "массив" к временному значению "указатель на элемент". Этот указатель является rvalue и нигде не хранится в памяти. Он существует только концептуально - как результат вышеупомянутого неявного преобразования. Это чисто воображаемый указатель.

По-английски явление "превращения" массива в указатель известно под устойчивым названием "array type decay". В стандарте языка С++ (где ситуация с массивами точно такая же) это неявное преобразование официально называется "array-to-pointer conversion".

Но это неявное преобразование применяется не всегда. В языке С существует три контекста-исключения, в которых это неявное преобразование не делается:

1) Оператор 'sizeof'. Как известно 'sizeof(array)' возвращает размер всего массива, а не размер указателя на элемент.

2) Унарный оператор '&'. Возвращает указатель типа "указатель-на-весь-массив", а не "указатель-на-указатель"

3) Инициализация массива строковым литералом - 'char a[] = "Hello";'. Литерал '"Hello"' является массивом , но не деградирует до указателя в данном контексте.

Таким образом, применение оператора '&' к объект типа "массив" дает вам указатель "на весь массив" (с точки зрения типа указателя). Т.е. для 'int a[20]' выражение '&a' имеет тип 'int (*)[20]'. В памяти адрес "всего массива" - это адрес того самого непрерывного блока, о котором я говорил выше. Т.е. численно этот адрес совпадает с адресом нулевого элемента массива. Именно поэтому выражения '&a', '&a[0]' и просто 'a' численно дают один и тот же адрес.

Именно поэтому, к примеру, в функцию 'memcpy' для двух

массивов

(т.е. именно объектов типа "массив", а не "указатель") можно вызывать и как 'memcpy(dst, src, sizeof src)', и как 'memcpy(&dst, &src, sizeof src) и как 'memcpy(&dst[0], &src[0], sizeof src). Результат будет один и тот же во всех случаях.

Именно поэтому правильно работает 'scanf' при использовании 'mass' или '&mass', ибо оба выражения дают одно и то же физическое численное значение.

Кстати, Денис Ритчи в своей статье об истории происхождения С (

http://cm.bell-labs.com/who/dmr/chist.html

) пишет, что изначально он собирался реализовать массивы в С точно так же, как они были реализованы в языках B и BCPL. В B и BCPL массивы были реализованы именно как самостоятельные

указатели

, указывающие на отдельные посторонние блоки памяти. Но Ритчи также хотел ввести в язык такой новый тип, как структуры. И тут сразу возникла проблема: если массив будет реализован через указатель, то структуру, содержащую в себе массивы, невозможно будет тривиально копировать. Т.е. обычный 'memcpy' будет тупо копировать указатели из одной структуры в другую. А это, разумеется, неприемлемо. По этой причине он отказался от идеи реализовывать массивы через физические указатели. Массивы в С стали просто блоками памяти. Никаких указателей. Но для сохранения похожей на B и BCPL семантики в язык С было введено неявное преобразование из типа "массив" в тип "указатель". Большинство операций с массивами работает именно через это преобразование, т.е. через временный указатель. Но физически этого указателя не существует.

Вернуться к обсуждению:

Распространенные ошибки C