Массивы - это указатели?

Возможный дубликат:
Является ли имя массива указателем в C?

Разнообразны ли массивы и указатели? Я столкнулся с этим вопросом, потому что в обоих случаях мы обращаемся к элементам из начального адреса элемента. Таким образом, между ними должна быть тесная связь. Пожалуйста, объясните точное соотношение между ними. Спасибо.

Ответы

Ответ 1

Давайте сначала извлеките важный материал: массивы не являются указателями. Типы массивов и типы указателей - это совершенно разные вещи и обрабатываются компилятором по-разному.

Если возникает путаница, то, как C обрабатывает выражения массива. N1570:

6.3.2.1 Lvalues, массивы и обозначения функций

...
3 За исключением случаев, когда это операнд оператора sizeof, оператор _Alignof или унарный оператор & или строковый литерал, используемый для инициализации массива, выражение, которое имеет type '' типа типа преобразуется в выражение с указателем типа '', чтобы набирать такие точки к исходному элементу объекта массива и не является значением lvalue. Если объект массива имеет зарегистрируйте класс хранения, поведение не определено.

Посмотрите на следующие объявления:

int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int *parr = arr;

arr - это 10-элементный массив int; это относится к непрерывному блоку памяти, достаточному для хранения значений 10 int. Выражение arr во втором объявлении имеет тип массива, но поскольку он не является операндом & или sizeof и не является строковым литералом, тип выражения становится "указателем на int", а значение - адрес первого элемента или &arr[0].

parr - указатель на int; это относится к блоку памяти, достаточно большому, чтобы удерживать адрес одного объекта int. Он инициализируется, чтобы указать на первый элемент в arr, как объяснялось выше.

Здесь представлена ​​гипотетическая карта памяти, показывающая взаимосвязь между ними (предполагая 16-битные int и 32-разрядные адреса):

Object           Address         0x00  0x01  0x02  0x03
------           -------         ----------------------
   arr           0x10008000      0x00  0x00  0x00  0x01
                 0x10008004      0x00  0x02  0x00  0x03
                 0x10008008      0x00  0x04  0x00  0x05
                 0x1000800c      0x00  0x06  0x00  0x07
                 0x10008010      0x00  0x08  0x00  0x09
  parr           0x10008014      0x10  0x00  0x80  0x00

Типы имеют значение для таких вещей, как sizeof и &; sizeof arr == 10 * sizeof (int), который в этом случае равен 20, тогда как sizeof parr == sizeof (int *), который в этом случае равен 4. Аналогично, тип выражения &arr равен int (*)[10] или указателю на 10-элементный массив int, тогда как тип &parr равен int ** или указателю на указатель на int.

Обратите внимание, что выражения arr и &arr будут давать одно и то же значение (адрес первого элемента в arr), но типы выражений различны (int * и int (*)[10], соответственно). Это имеет значение при использовании арифметики указателя. Например, данный:

int arr[10] = {0,1,2,3,4,5,6,7,8,9};
int *p = arr;
int (*ap)[10] = &arr;

printf("before: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap);
p++;
ap++;
printf("after: arr = %p, p = %p, ap = %p\n", (void *) arr, (void *) p, (void *) ap);

строка "before" должна печатать одинаковые значения для всех трех выражений (на нашей гипотетической карте, 0x10008000). Строка "после" должна показывать три разных значения: 0x10008000, 0x10008002 (base plus sizeof (int)) и 0x10008014 (base plus sizeof (int [10])).

Теперь вернемся ко второму абзацу выше: выражения массива в большинстве случаев преобразуются в типы указателей. Посмотрим на выражение подстроки arr[i]. Поскольку выражение arr не является операндом либо sizeof, либо &, и поскольку он не является строковым литералом, используемым для инициализации другого массива, его тип преобразуется из "10-элементного массива int" to "на int", и операция индекса применяется к этому значению указателя. Действительно, когда вы смотрите на определение языка C, вы видите следующий язык:

6.5.2.1 Подписывание массива
...
2 Постфиксное выражение, за которым следует выражение в квадратных скобках [], является индексированным обозначением элемента объекта массива. Определение нижнего индекса [] состоит в том, что E1 [E2] идентично (* ((E1) + (E2))), Из-за правил преобразования, которые применяются к двоичному + оператору, если E1 является объектом массива (эквивалентно указателю на исходный элемент объекта массива) и E2 целое число, E1 [E2] обозначает E2 -й элемент E1 (с нуля).

В практическом плане это означает, что вы можете применить оператор индекса к объекту-указателю, как если бы он был массивом. Вот почему код вроде

int foo(int *p, size_t size)
{
  int sum = 0;
  int i;
  for (i = 0; i < size; i++)
  {
    sum += p[i];
  }
  return sum;
}

int main(void)
{
  int arr[10] = {0,1,2,3,4,5,6,7,8,9};
  int result = foo(arr, sizeof arr / sizeof arr[0]);
  ...
}

работает так, как он делает. main имеет дело с массивом int, тогда как foo имеет дело с указателем на int, но оба могут использовать индексный оператор, как если бы они имели дело с типом массива.

Это также означает, что подтипирование массива является коммутативным: если a является выражением массива, а i является целочисленным выражением, a[i] и i[a] являются действительными выражениями, и оба значения будут иметь одинаковое значение.

Ответ 2

Не знаю о С++. Для C c-faq отвечает намного лучше, чем когда-либо.

Небольшой фрагмент из c-faq:

6.3 Итак, что подразумевается под "эквивалентностью указателей и массивов" в C?

[...]

В частности, краеугольным камнем эквивалентности является это ключевое определение:

     
  

Ссылка на объект типа array-of-T, который появляется в     (с тремя исключениями) превращается в указатель на его     первый элемент; тип результирующего указателя - указатель на T.

  
  

[...]

Ответ 3

В С++ в соответствии со стандартом С++ 4.2:

Значение lvalue или rvalue типа "массив N T" или "массив неизвестной границы T" может быть преобразовано в rvalue типа "указатель на Т." Результатом является указатель на первый элемент массив.

Ответ 4

Нет, они не реализованы по-разному. Оба находят элементы с одинаковым вычислением: a[i] находится по адресу a + i*sizeof(a[0]), также p[i] находится по адресу p + i*sizeof(p[0]).

Но, , они обрабатываются по-разному с помощью системы типов. С++ печатает информацию о массивах, которые можно увидеть с помощью sizeof operator (например, C), вывода шаблона, перегрузки функций, RTTI и т.д. В основном где-либо на языке, который использует информацию типа, для указателей и массивов можно вести себя по-разному.

В С++ существует много примеров, где две разные языковые концепции имеют одну и ту же реализацию. Всего несколько: массивы против указателей, указатели против ссылок, виртуальные функции и указатели на функции, итераторы против указателей, для циклов против циклов, исключений vs longjmp

В каждом случае существует другой синтаксис и другое представление о двух концепциях, но в конечном итоге они приводят к тому же машинного кода.

Ответ 5

В С++ (и в C тоже я думаю) массив не является указателем, и это можно доказать следующим образом.

#include <iostream>
int main()
{
   char arr[1000];
   std::cout << sizeof arr;
}

Если arr был указателем, эта программа будет печатать sizeof (char *), который обычно равен 4. Но он печатает 1000.

другое доказательство:

template <class T>
void f(T& obj)
{
   T x = obj; //this will fail to compile if T is an array type
}

int main()
{
   int a[30] = {};
   int* p = 0; 
   f(p); //OK
   f(a); //results in compile error. Remember f takes by ref therefore needs lvalue and no conversion applies
}

Формально массив преобразуется в указатель на его первый элемент в преобразованиях lvalue-to-rvalue, то есть когда lvalue типа массива задается в контексте, когда ожидается значение r, массив преобразуется в указатель к его первому элементу.

Кроме того, функция, объявленная для принятия массива по значению, эквивалентна функции, принимающей указатель, то есть

void f(int a[]);
void f(int a[10]);
void f(int* a);

- три эквивалентных объявления. НТН

Ответ 6

В С++ массив имеет атрибут размера, поэтому для

T a[10];
T b[20];

a и b имеют разные типы.

Это позволяет использовать такой код

template<typename T, size_t N>
void foo(T (&a)[N])
{
   ...
}

Ответ 7

Самая большая точка путаницы между массивами и указателями исходит из решения K & R, чтобы сделать параметры функции, которые объявлены как тип массива, ведут себя так, как если бы они были объявлены как указатели. Объявления

void foo(int a[]);
и
void foo(int *a);
эквивалентны, как есть (насколько я могу судить)
void foo(int a[5]);
, хотя я не уверен, что компилятор должен был бы принять ссылку на [6] в последней функции. В других контекстах объявление массива выделяет пространство для указанного количества элементов. Обратите внимание, что данный:
typedef int foo[1];

любое объявление типа foo будет выделять пространство для одного элемента, но любая попытка передать foo как параметр функции вместо этого передаст адрес. Что-то полезное трюк, который я узнал при изучении реализации va_list.