Задать вопрос
ali_aliev
@ali_aliev
Разработчик на Django/Python, JavaScript

Странное поведение массивов в Си

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

Имеем массив:
#include <stdio.h>

int main(int argc, char *argv[]) {
    char *name = "Hello World";
    printf("%p", &name);
    printf("%p", name);
    printf("%s", name);
}


Почему первый и второй принт выдает адрес первого элемента в массиве и при этом во время обращении к name возвращается вся строка да и еще sizeof(name) возвращает размер всей строки в массиве? Есть еще один вопрос по поводу массивов: элемент массива можно получить вот так (то есть работать с ними как с указателями) *(name + 1), получается что сами по себе массивы являются указателями? Где об этом можно подробнее прочитать?

P.S. Зачем нужны указатели на указатели?

Заранее, благодарю.
  • Вопрос задан
  • 3889 просмотров
Подписаться 5 Оценить Комментировать
Решения вопроса 1
ntkt
@ntkt
Потомственный рыцарь клавиатуры и паяльника
Массив как переменная в C — это псевдоним адреса в памяти, по которому лежит его первый элемент.
Массива ни в машинном коде, ни в памяти, строго говоря, не существует. Существует только его «содержимое» — его элементы.
При обращении к элементу массива действия производятся машиной прямо над его содержимым по вычисленному адресу.
Самого массива — не существует.
Всякий раз, когда в С Вы работаете с массивом — компилятор использует или указатель на первый элемент массива (например, когда Вы передаете массив в функцию), или адрес первого элемента массива, а машина далее с этим работает.

Указатель в C — это переменная, содержащая адрес в памяти.
Указатель в машинном коде и памяти — существует. Он имеет значение — число размером с разрядность машины, а значение числа — это адрес в памяти.
Указатель как число — может быть передан в регистрах, лежать на стеке, и т.д.
При обращении к байту/слову/двойному слову/… по смещению через указатель, в машинном коде указатель разыменовывается (в явном виде берется его значение=адрес) т.е. в машинном коде происходят несколько другие действия, чем в случае для массива.

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

Вот, кстати, приличное описание различий на английском:
eli.thegreenplace.net/2009/10/21/are-pointers-and-arrays-equivalent-in-c/
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 6
@mickvav
Programmer, system and network administrator
Керниган, Ричи — язык C.http://lib.ru/CTOTOR/kernigan.txt
Да, массивы — это указатели. Указатель на указатель нужен, например, когда вы хотите сказать функции «а сделай нечто и положи результат неизвестной мне пока длины сюда». Вы просто передаете функции указатель на указатель, она аллокирует/реаллокирует, чего ей надо, а вы уже с результатом работаете.
Ответ написан
Комментировать
@mickvav
Programmer, system and network administrator
Тут, видимо, фокус в том, что вы создали константный массив при инициализации. Что-то мне подсказывает, что такое поведение (адрес указателя равен адресу самого массива) характерно именно для такой ситуации-компилятор решает, что раз массив-константа в стеке, зачем под ее адрес отдельную память выделять? Там же на стадии компиляции все известно… Но скорее всего данное поведение-compiler-specific example of bad code ;)
Ответ написан
@mickvav
Programmer, system and network administrator
По вопросу — проверил, у меня выдает разные адреса. Вы на каком компиляторе сие увидели?
Ответ написан
Первый и второй принт выдадут разные результаты.

Массив размещается в стеке, там же размещается переменная name, где хранится адрес на первый элемент массива. В конец массива char компилятор добавляет символ с кодом 0x00 — признак конца строки. Именно по этому printf выводит правильное количество символов.

Первый принт выдаст адрес переменной name, а второй — содержимое (адрес символа 'H').

Третий же выведет все символы начиная с адреса, хранящегося в переменной name, пока не встретится 0x00.

sizeof выполняется на этапе компиляции, в машинном коде вместо него будут константы.
sizeof(name) вернёт размер указателя, а не размер массива, потому что вы определили не массив, а указатель, и присвоили ему адрес массива.

char name_array[] = "Hello World!"

Вот это уже массив, и sizeoff вернёт его размер, потому что он известен на этапе компиляции.

С указателями можно проводить арифметические операции. К примеру *(name+i) равносильно name[i].
К адресу прибавится не i, а i*sizeof(*name), т.е. размер типа указателя помноженный на число. Получаем i-ый элемент. Об этом позаботится компилятор. Для получения произвольного адреса необходимо привести указатель к типу void.

Массивы не являются указателями. Есть массивы, есть указатели. Есть адреса. Берём адрес массива и сохраняем его в указатель. В свою очередь указатель — это переменная, и она тоже располагается по какому-то адресу. Тут появляются указатели на указатели (иерархия не ограничена). К примеру многомерные массивы.
Ответ написан
sizeof(name) возвращает размер всей строки в массиве

тут вы что-то путаете, должен возвращать размер указателя, т.е. 4 или 8 в зависимости от разрядности платформы.

У вас в примере вообще нет массивов, кроме как в инициализаторе. Массив это

char name[]={...}

в таком случае name является константой указывающей на массив, sizeof для нее — размер массива, константа не обладает собственным адресом, поэтому &name в данном случае возвращает адрес массива, т.е. собственно name. Во всем остальном она полностью эквивалентна const char *, по этой причине можно использовать *(name +i).

То, что при инициализации в скобочках можно не указывать размер — это особенность инициализации в C, при наличии инициализатора размер вычисляется при компиляции.

char* name={...} отличается от предыдущего примера тем, что name является не константой, а переменной, под которую выделяется дополнительная память (4/8 байт на 32/64-битных системах соответственно). Соответственно, sizeof(name) возвращает размер переменной, а &name вернет адрес этой переменной.
Ответ написан
Комментировать
@mickvav
Programmer, system and network administrator
Если написать

char a[]="hello";

То sizeof(a) будет 6. В gcc, который у меня под рукой. E.g. сколько стека под этот массив отдали.
А если

char *a="hello";

То sizeof(a) будет 8 E.g. размер указателя. И &a будет(ожидаемо) отличаться от a.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы