Является ли имя массива указателем?


203

Является ли имя массива указателем в C? Если нет, в чем разница между именем массива и переменной-указателем?


4
Но массив тот же & array [0]

36
@pst: возвращает &array[0]указатель, а не массив;)
jalf

28
@Nava (и pst): array и & array [0] на самом деле не одно и то же. Пример: sizeof (массив) и sizeof (& массив [0]) дают разные результаты.
Томас Падрон-Маккарти

1
@ Томас согласен, но с точки зрения указателей, когда вы разыменовываете массив и & array [0], они выдают одинаковое значение array [0] .ie * array == array [0]. Никто не имел в виду, что эти два указателя одинаковы, но в данном конкретном случае (указывая на первый элемент) вы также можете использовать имя массива.
Нава Кармон

1
Это также может помочь вам понять: stackoverflow.com/questions/381542 , stackoverflow.com/questions/660752
Дина

Ответы:


255

Массив - это массив, а указатель - это указатель, но в большинстве случаев имена массивов преобразуются в указатели. Термин часто используется, что они распадаются на указатели.

Вот массив:

int a[7];

a содержит место для семи целых чисел, и вы можете поместить значение в одно из них с присваиванием, например так:

a[3] = 9;

Вот указатель:

int *p;

pне содержит пробелов для целых чисел, но может указывать на пробел для целых чисел. Мы можем, например, установить его так, чтобы он указывал на одно из мест в массиве a, например на первое:

p = &a[0];

Что может сбить с толку, так это то, что вы также можете написать это:

p = a;

Это не копирует содержимое массива aв указатель p(что бы это ни значило). Вместо этого имя массива aпреобразуется в указатель на его первый элемент. Так что это назначение делает то же самое, что и предыдущее.

Теперь вы можете использовать pаналогично массиву:

p[3] = 17;

Причина этого заключается в том, что оператор разыменования массива в C, [ ]определяется в терминах указателей. x[y]означает: начать с указателя x, шаг yвперед элементы после того, на что указывает указатель, а затем взять все, что есть. Используя арифметический синтаксис указателя, x[y]также можно записать как *(x+y).

Чтобы это работало с обычным массивом, таким как our a, имя aв a[3]должно быть сначала преобразовано в указатель (на первый элемент в a). Затем мы продвигаемся на 3 элемента вперед и берем все, что есть. Другими словами: возьмите элемент в позицию 3 в массиве. (Который является четвертым элементом в массиве, поскольку первый из них пронумерован 0.)

Итак, в итоге, имена массивов в программе на C (в большинстве случаев) преобразуются в указатели. Единственное исключение - когда мы используем sizeofоператор в массиве. Если бы он aбыл преобразован в указатель в этом контексте, он sizeof aбы дал размер указателя, а не фактического массива, что было бы довольно бесполезно, поэтому в этом случае aподразумевается сам массив.


5
Аналогичное автоматическое преобразование применяется к указателям на функции - и то functionpointer()и другое и (*functionpointer)(), как ни странно, означают одно и то же.
Карл Норум

3
Он не спрашивал, совпадают ли массивы и указатели, но является ли имя массива указателем
Рикардо Аморес

32
Имя массива не является указателем. Это идентификатор переменной типа array, которая имеет неявное преобразование в указатель типа элемента.
Павел Минаев

29
Кроме того, кроме того sizeof(), другой контекст, в котором нет спада массива-> указатель, является оператором &- в приведенном выше примере &aбудет указатель на массив из 7 int, а не указатель на единицу int; то есть, его тип будет int(*)[7], который неявно конвертируется в int*. Таким образом, функции могут на самом деле брать указатели на массивы определенного размера и применять ограничение через систему типов.
Павел Минаев

3
@ onmyway133, проверьте здесь короткое объяснение и дополнительные цитаты.
Карл Норум

37

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

int arr[7];

/* arr used as value */
foo(arr);
int x = *(arr + 1); /* same as arr[1] */

/* arr not used as value */
size_t bytes = sizeof arr;
void *q = &arr; /* void pointers are compatible with pointers to any object */

20

Если выражение типа массива (например, имя массива) появляется в большем выражении и не является операндом ни оператора, &ни sizeofоператора, то тип выражения массива преобразуется из «N-элементного массива T» в «указатель на T», а значением выражения является адрес первого элемента в массиве.

Короче говоря, имя массива не является указателем, но в большинстве случаев оно рассматривается как указатель.

редактировать

Отвечая на вопрос в комментарии:

Если я использую sizeof, я считаю размер только элементов массива? Тогда массив «head» также занимает место с информацией о длине и указателем (а это значит, что он занимает больше места, чем обычный указатель)?

При создании массива выделяется только пространство для самих элементов; не хранится хранилище для отдельного указателя или каких-либо метаданных. Дано

char a[10];

что вы получаете в памяти

   +---+
a: |   | a[0]
   +---+ 
   |   | a[1]
   +---+
   |   | a[2]
   +---+
    ...
   +---+
   |   | a[9]
   +---+

Выражение a относится ко всему массиву, но нет никакого объекта a отдельно от самих элементов массива. Таким образом, sizeof aдает вам размер (в байтах) всего массива. Выражение &aдает вам адрес массива, который совпадает с адресом первого элемента . Разница между &aи &a[0]составляет тип результата 1 - char (*)[10]в первом случае и char *во втором.

Все становится странным, когда вы хотите получить доступ к отдельным элементам - выражение a[i]определяется как результат *(a + i)- с учетом значения адреса a, iэлементов смещения ( не байтов ) от этого адреса и разыменования результата.

Проблема в том, что aэто не указатель или адрес, а весь объект массива. Таким образом, правило в C, что всякий раз, когда компилятор видит выражение типа массива (например a, типа, который имеет тип char [10]), и это выражение не является операндом sizeofили унарным &оператором, тип этого выражения конвертируется («распадается») указатель на тип ( char *), а значением выражения является адрес первого элемента массива. Следовательно, выражение a имеет тот же тип и значение, что и выражение &a[0](и, соответственно, выражение *aимеет тот же тип и значение, что и выражение a[0]).

C был получен из более раннего языка под названием B, и в B a был отдельный объект указателя из элементов массива a[0], a[1]и т.д. Ричи хотел сохранить семантику массива B, но он не хотел возиться с хранением отдельного объекта указателя. Таким образом, он избавился от этого. Вместо этого компилятор будет преобразовывать выражения массива в выражения указателя во время перевода по мере необходимости.

Помните, что я сказал, что массивы не хранят метаданные об их размере. Как только это выражение массива «распадается» на указатель, все, что у вас есть, - это указатель на один элемент. Этот элемент может быть первым из последовательности элементов или может быть отдельным объектом. Там нет никакого способа узнать на основе самого указателя.

Когда вы передаете выражение массива в функцию, все, что получает функция - это указатель на первый элемент - он не знает, насколько большой массив (именно поэтому getsфункция представляла такую ​​угрозу и была в конечном итоге удалена из библиотеки). Чтобы функция знала, сколько элементов в массиве, вы должны либо использовать значение часового (например, терминатор 0 в строках C), либо вы должны передать количество элементов в качестве отдельного параметра.


  1. Какая * может * повлиять на интерпретацию значения адреса - зависит от машины.

Давно искал этот ответ. Спасибо! И если вы знаете, не могли бы вы рассказать немного дальше, что такое выражение массива. Если я использую sizeof, я считаю размер только элементов массива? Тогда массив «head» также занимает место с информацией о длине и указателем (а это значит, что он занимает больше места, чем обычный указатель)?
Андрей Дмитрук

И вот еще. Массив длины 5 имеет тип int [5]. То есть откуда мы знаем длину, когда мы вызываем sizeof (array) - по его типу? А это значит, что массивы разной длины похожи на разные типы констант?
Андрей Дмитрук

@AndriyDmytruk: sizeofявляется оператором, и он оценивает число байтов в операнде (или выражение, обозначающее объект, или имя типа в скобках). Таким образом, для массива sizeofвычисляется количество элементов, умноженное на количество байтов в одном элементе. Если intширина 4 байта, то массив из 5 элементов intзанимает 20 байтов.
Джон Боде

Разве оператор [ ]тоже не особенный? Например, int a[2][3];тогда for x = a[1][2];, хотя он может быть переписан как x = *( *(a+1) + 2 );, здесь aне преобразуется в тип указателя int*(хотя if aявляется аргументом функции, в которую он должен быть преобразован int*).
Стэн

2
@Stan: выражение aимеет тип int [2][3], который «распадается» для ввода int (*)[3]. Выражение *(a + 1)имеет тип int [3], который «распадается» на int *. Таким образом, *(*(a + 1) + 2)будет иметь тип int. aуказывает на первый 3-элементный массив int, a + 1указывает на второй 3-элементный массив int, *(a + 1) является вторым 3-элементным массивом int, *(a + 1) + 2указывает на третий элемент второго массива int, так же *(*(a + 1) + 2) как и третий элемент второго массива int, Как это отображается в машинном коде, полностью зависит от компилятора.
Джон Боде

5

Массив объявлен так

int a[10];

выделяет память на 10 intс. Вы не можете изменить, aно вы можете сделать арифметику указателя с a.

Такой указатель выделяет память только для указателя p:

int *p;

Он не выделяет никаких ints. Вы можете изменить это:

p = a;

и использовать индексы массива, как вы можете с:

p[2] = 5;
a[2] = 5;    // same
*(p+2) = 5;  // same effect
*(a+2) = 5;  // same effect

2
Массивы не всегда располагаются в стеке. Это детали реализации, которые будут варьироваться от компилятора к компилятору. В большинстве случаев статические или глобальные массивы будут выделяться из области памяти, отличной от стека. Массивы константных типов могут быть выделены из еще одной области памяти
Марк Бесси

1
Я думаю, что Грумдриг хотел сказать «выделяет 10 intсекунд с автоматической продолжительностью хранения».
Гонки на Легкость на Орбите

4

Имя массива само по себе дает место в памяти, поэтому вы можете рассматривать имя массива как указатель:

int a[7];

a[0] = 1976;
a[1] = 1984;

printf("memory location of a: %p", a);

printf("value at memory location %p is %d", a, *a);

И другие изящные вещи, которые вы можете сделать для указателя (например, добавление / вычитание смещения), вы также можете сделать с массивом:

printf("value at memory location %p is %d", a + 1, *(a + 1));

По языку, если C не представляет массив как просто своего рода «указатель» (педантично, это просто место в памяти. Он не может указывать на произвольное место в памяти и не может контролироваться программистом). Нам всегда нужно кодировать это:

printf("value at memory location %p is %d", &a[1], a[1]);

1

Я думаю, что этот пример проливает свет на проблему:

#include <stdio.h>
int main()
{
        int a[3] = {9, 10, 11};
        int **b = &a;

        printf("a == &a: %d\n", a == b);
        return 0;
}

Он прекрасно компилируется (с 2 предупреждениями) в gcc 4.9.2 и печатает следующее:

a == &a: 1

упс :-)

Итак, вывод - нет, массив не является указателем, он не хранится в памяти (даже не только для чтения) в качестве указателя, даже если он выглядит так, как вы, так как вы можете получить его адрес с помощью оператора & , Но, к сожалению, этот оператор не работает :-)), в любом случае, вы были предупреждены:

p.c: In function main’:
pp.c:6:12: warning: initialization from incompatible pointer type
  int **b = &a;
            ^
p.c:8:28: warning: comparison of distinct pointer types lacks a cast
  printf("a == &a: %d\n", a == b);

C ++ отказывается от любых таких попыток с ошибками во время компиляции.

Редактировать:

Вот что я хотел продемонстрировать:

#include <stdio.h>
int main()
{
    int a[3] = {9, 10, 11};
    void *c = a;

    void *b = &a;
    void *d = &c;

    printf("a == &a: %d\n", a == b);
    printf("c == &c: %d\n", c == d);
    return 0;
}

Хотя cи a«указывают» на одну и ту же память, вы можете получить адрес cуказателя, но вы не можете получить адрес aуказателя.


1
Msgstr "Компилируется нормально (с 2 предупреждениями)". Это не хорошо Если вы скажете gcc скомпилировать его как правильный стандарт C, добавив -std=c11 -pedantic-errors, вы получите ошибку компилятора для написания недопустимого кода C. Причина в том, что вы пытаетесь присвоить int (*)[3]переменную типа int**, которые являются двумя типами, которые не имеют абсолютно никакого отношения друг к другу. Так что этот пример должен доказать, я понятия не имею.
Лундин

Спасибо, Лундин, за ваш комментарий. Вы знаете, что есть много стандартов. Я попытался уточнить, что я имел в виду в редактировании. int **Тип не точка там, следует лучше использовать void *для этого.
Пало

-3

Имя массива ведет себя как указатель и указывает на первый элемент массива. Пример:

int a[]={1,2,3};
printf("%p\n",a);     //result is similar to 0x7fff6fe40bc0
printf("%p\n",&a[0]); //result is similar to 0x7fff6fe40bc0

Оба оператора печати выдают одинаковый вывод для машины. В моей системе это дало:

0x7fff6fe40bc0

-4

Массив - это совокупность последовательных и смежных элементов в памяти. В C имя массива является индексом первого элемента, и применяя смещение, вы можете получить доступ к остальным элементам. «Индекс к первому элементу» действительно является указателем на направление памяти.

Разница с переменными-указателями в том, что вы не можете изменить местоположение, на которое указывает имя массива, поэтому оно похоже на константный указатель (это похоже, а не то же самое. См. Комментарий Марка). Но также то, что вам не нужно разыменовывать имя массива, чтобы получить значение, если вы используете арифметику указателей:

char array = "hello wordl";
char* ptr = array;

char c = array[2]; //array[2] holds the character 'l'
char *c1 = ptr[2]; //ptr[2] holds a memory direction that holds the character 'l'

Так что ответ вроде «да».


1
Имя массива не совпадает с константным указателем. Дано: int a [10]; int * p = a; sizeof (p) и sizeof (a) не совпадают.
Марк Бесси

1
Есть и другие отличия. В общем, лучше придерживаться терминологии, используемой стандартом C, который конкретно называет это «преобразованием». Цитата: «За исключением случаев, когда это операнд оператора sizeof или унарный оператор &, или строковый литерал, используемый для инициализации массива, выражение с типом« массив типа »преобразуется в выражение с типом« 'указатель на тип' ', который указывает на начальный элемент объекта массива и не является lvalue. Если объект массива имеет класс хранения регистров, поведение не определено. "
Павел Минаев

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.