Каковы последствия передачи и назначения массивов в качестве указателей в C ++? - PullRequest
11 голосов
/ 18 августа 2010

В качестве фона я дал ответ на этот пост некоторое время назад:

Возвращаемый массив в функции

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

Этот вопрос не предназначен для приманки троллей, я понимаю, что указатель и массив - это не одно и то же, но некоторый доступный синтаксис в языке C ++, безусловно, заставляет их вести себя очень схожим образом во многих случаях. (К вашему сведению, мой компилятор i686-apple-darwin9-g++-4.0.1 на OS X 10.5.8)

Например, этот код компилируется и работает нормально для меня (я понимаю, x[8] - потенциальная ошибка сегментации):

  //this is just a simple pointer                                                                                                                                                            
  int *x = new int;
  cout << x << " " << (*x) << " " << x[8] << endl; //might segfault                                                                                                                          

  //this is a dynamic array                                                                                                                                                                  
  int* y = new int[10];
  cout << y << " " << (*y) << " " << y[8] << endl;

  //this is a static array                                                                                                                                                                   
  int z[10];
  cout << z << " " << (*z) << " " << z[8] << endl;

Этот конкретный фрагмент выглядит так, что указатели и массивы можно использовать почти одинаково, но если я добавлю это в конец этого кода, последние две строки не скомпилируются:

  x = y;
  x = z;
  y = x;
  y = z;
  //z = x; //won't compile
  //z = y; //won't compile

Итак, компилятор, по крайней мере, понимает, что z и x - это разные вещи, но я могу поменять x и y просто отлично.

Это еще более сбивает с толку, когда вы смотрите на передачу массивов в функции и возвращение массивов из функций. Рассмотрим этот пример (опять же, я знаю о возможных ошибках сегментации при передаче x):

void foo(int in[])
{
  cout << in[8] << endl;                                                                                                                                                                                      
}

void bar(int* in)
{
  cout << in[8] << endl;                                                                                                                                                                     
}

int main()
{
  //this is just a simple pointer                                                                                                                                                            
  int *x = new int;
  foo(x);
  bar(x);

  //this is a dynamic array                                                                                                                                                                  
  int* y = new int[10];
  foo(y);
  bar(y);

  //this is a static array                                                                                                                                                                   
  int z[10];
  foo(z);
  bar(z);
}

Весь этот код правильно компилируется и запускается на моем компьютере.

Я чувствую, что у меня есть приличное внутреннее понимание того, что здесь происходит, но если бы вы попросили меня четко сформулировать, что происходит, я не чувствую, что могу удовлетворительно объяснить. Итак, вот что я получаю:

  • Когда я передаю массив функции как int* in вместо int in[], что я получаю или проигрываю? То же самое при возврате массива как int*? Есть ли когда-нибудь плохие побочные эффекты от этого?

  • Если бы я спросил вас, что тип данных y, вы бы сказали, указатель на int, массив целых или что-то еще?

  • Точно так же, что происходит, когда я говорю x = y против x = z? Я все еще могу использовать x[] и получить доступ к вещам, которые были изначально в y или z, но действительно ли это только потому, что арифметика с указателями попадает мне в пространство памяти, которое все еще действует?

Я перебрал все подобные вопросы о массивах / указателях на SO, и у меня возникли проблемы с поиском окончательного объяснения, которое прояснит это для меня раз и навсегда.

Ответы [ 8 ]

6 голосов
/ 18 августа 2010

Принципиальная разница между указателем и массивом состоит в том, что указатель имеет уникальный адрес памяти, который содержит адрес данных массива.

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

Именно поэтому вы можете присвоить его значение другому указателю, но не наоборот. Нет места в памяти указателя для обработки в качестве l-значения.

6 голосов
/ 18 августа 2010

C ++ имеет статическую типизацию, поэтому, конечно, компилятор понимает, что x и z - это не одно и то же.Они имеют разные типы - z - это массив, x и y - указатели.

Причина, по которой z = x не компилируется, состоит не в (просто) в несовместимости типов, а в том, что вы можете 't присваивать переменной массива вообще.Когда-либо.x = z присваивает x указатель на первый элемент z.x = y присваивает значение y x. [*]

Когда я передаю массив функции как int * in вместо int in [], что я получаюили проигрывают?

Они делают одно и то же, поэтому у вас нет выбора.Возможно, вас сбило с толку тот факт, что синтаксис C ++ допускает int in[] в качестве параметра функции.Тип параметра in это не какой-либо массив, это int*.

Если бы я спросил вас, какой тип данных у равен

Это int*.Это то, что объявлено как, так оно и есть.

Значение , которое содержит , является указателем на (первый элемент) массива.Я часто использую эту формулу: «указатель на (первый элемент)» в тех случаях, когда я хотел бы сказать «указатель на массив», но не могу, потому что есть вероятность двусмысленности относительно того, является ли используемый тип указателем-to-array or not.

Однако указатели на массивы редко используются в C ++, потому что размер массива является частью типа.В C ++ нет такого типа, как «указатель на массив int», просто «указатель на массив 1 int», «указатель на массив 2 int» и т. Д. Это обычно не очень удобно, поэтому использованиеуказатель на первый элемент массива, размер которого может быть неизвестен во время компиляции.

- это действительно только потому, что арифметика с указателями попадает в пространство памяти, которое все еще действует

В значительной степени, да.Размер массива является частью типа z, но не является частью типа x или y, а также не является частью типа результата z, затухающего в указателе на его первый элемент.Так что y может быть указателем на первый из 10 элементов или просто на 1 элемент.Вы знаете разницу только по контексту, и, требуя от своих абонентов, чтобы значение, которое вы указали, указывало на то, на что он должен указывать.

«Случается», однако, слишком много оставляет за собой шанс - часть вашей работыпри использовании массивов необходимо убедиться, что вы не отклоняетесь от их границ.

[*] z = x не разрешено, даже после того, как вы сделали x = z, потому что z есть (и всегда будетбыть) конкретный массив из 10 дюймов в памяти.Еще при разработке C возник вопрос о том, могут ли переменные массива в принципе быть «перезаписываемыми», что означает, что вы можете сделать:

int z[10];
int y[10];
z = y; // z is now an alias for y
y[0] = 3;
// z[0] now has the value 3

Деннис Ричи решил не допустить этого, потому что это помешало бы емуот различения массивов от указателей так, как он должен был сделать.Так что z никогда не может ссылаться на массив, отличный от того, который был объявлен как.Прочитайте все об этом здесь: http://cm.bell -labs.com / cm / cs / who / dmr / chist.html , под "Embryonic C".

Еще одно вероятное значение для z = y может быть memcpy(z,y,sizeof(z)).Этого значения тоже не было.

5 голосов
/ 18 августа 2010

Массивы не являются указателями, но массивы легко распадаются на указатели на их первый элемент. Кроме того, C (и, следовательно, C ++) допускает использование синтаксиса доступа к массиву для указателей .

Когда я передаю массив функции как int * in вместо int в [], что я получаю или теряю? То же самое при возврате массива как int *? Есть ли когда-нибудь плохие побочные эффекты от этого?

Вы ничего не получаете, потому что int[] это просто еще один способ написать int*. Если вы хотите передать массив, вы должны передать его по ссылке , точно соответствующей его размеру. Нетипичные аргументы шаблона могут облегчить проблему с точным размером:

template< std:::size_t N >
void f(int (&arr)[N])
{
   ...
}

Если бы я спросил вас, какой тип данных у, вы бы сказали указатель на int, массив целых или что-то еще?

Это указатель на первый элемент динамически размещаемого массива.

Точно так же, что происходит, когда я говорю x = y против x = z?

Вы присваиваете адреса разных объектов разных типов одному и тому же указателю. (И вы пропускаете int в кучу. :))

Я все еще могу использовать x [] и получить доступ к вещам, которые были изначально в y или z, но действительно ли это только потому, что арифметика указателей попадает мне в пространство памяти, которое все еще допустимо?

Да. Как я уже сказал, указатели удобно и запутанно позволяют применять к ним синтаксис массива. Однако, это все еще не делает указатель массивом.

2 голосов
/ 18 августа 2010

Вот фрагмент из этой книги (и семантика C ++ вытекает из его обратной совместимости с C).Указатели массива "are" в следующих случаях:

  1. Имя массива в выражении (в отличие от объявления) обрабатывается компилятором как указатель на первый элементмассива (это не относится к sizeof) (стандарт ANSI C, 6.2.2.1)
  2. Индекс всегда эквивалентен смещению от указателя (6.3.2.1)
  3. Имя массива в объявлении параметра функции обрабатывается компилятором как указатель на первый элемент массива (6.7.1)

Это в основном означает, что:

int arr[20]; int* p = arr;

эквивалентно:

int arr[20]; int* p = &arr[0];

Тогда

int arr[20]; int x = arr[10];

эквивалентно:

int arr[20]; int x = *( arr + 10 );

И

void func( int arr[] );

эквивалентно:

void func( int* arr );

С другой стороны, указатели никогда не преобразуются обратно в массивы - поэтому последние две строки не компилируются.

1 голос
/ 18 августа 2010

В качестве отступления (что имеет какое-то отношение к теме - можно было бы добавить это в качестве комментария, но не иметь достаточно реп.) - вы можете измерить количество элементов в массиве по сравнению с использованием указателя.Иначе говоря, sizeof возвращает sizeof (array_type) * num_elements_in_array против возврата размера указателя.Для этой цели Glib предоставляет этот макрос .

1 голос
/ 18 августа 2010
  1. Нет никакой разницы (вообще) между такими параметрами функции, как int *in и int in[]. Для параметра функции это просто разные варианты написания pointer to T. Единственный способ, которым они могут различаться - это (возможно) что-то вроде читабельности (например, если вы намереваетесь всегда передавать базовый адрес массива, вы можете найти нотацию массива более подходящей, тогда как если вы намереваетесь передать адрес одного объект, вы могли бы найти обозначение указателя более со вкусом).
  2. В приведенном выше коде y явно имеет тип pointer to int.
  3. x и y - указатели, которые могут быть назначены. z - массив, который не может быть назначен.
1 голос
/ 18 августа 2010

Когда я передаю массив функции как int * in вместо int in [], что я получить или потерять? Это то же самое, правда при возврате массива как int *? Являются там когда-либо плохие побочные эффекты от выполнения это? * * 1002

AFAIK, один просто синтаксический сахар для другого, и они означают точно то же самое.

Версия с [], вероятно, просто дает намек на то, что эта функция ожидает указатель на массив, а не указатель на отдельный объект.

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

Если бы я спросил вас, какой тип данных у есть, вы бы сказали указатель на int, массив целых или что-то еще?

Тип y является указателем на int. На самом деле, в случае динамически размещаемого массива вы никогда не увидите массив вообще! То есть нет способа определить размер выделения с помощью sizeof, в отличие от реальных массивов.

Точно так же, что происходит, когда я говорю х = у против х = г? Я все еще могу использовать х [] и получить доступ к вещам, которые были первоначально в у или г, но это на самом деле только потому, что указатель арифметики случается посадить меня в пространство памяти что еще действует?

Это потому, что x является указателем. Вы не сможете выполнить z = x;, поскольку не можете назначить массивы.

0 голосов
/ 18 августа 2010

Когда я передаю массив функции как int * in вместо int in [], что я получаю или проигрываю?То же самое при возврате массива как int *?Есть ли когда-нибудь плохие побочные эффекты от этого?

Вы ничего не получаете или не теряете

Если бы я спросил вас, что тип данных у, вы бы сказали указательint, массив целых или что-то еще?

Я бы назвал тебя указателем на массив целых чисел

Аналогично, что происходит, когда я говорю x = y противх = я?Я все еще могу использовать x [] и получить доступ к вещам, которые были изначально в y или z, но действительно ли это только потому, что арифметика указателей попадает мне в область памяти, которая все еще действительна?

x = y делает не делает копию массива, на который указывает y, только копию указателя в y.

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

Также свободная выделенная память .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...