What are the ramifications of passing & assigning arrays as pointers in C++?

As background, I gave an answer to this post a little while ago:

Return array in a function

And it unintentionally kicked off a really long comment chain about pointers vs. arrays in C++ because I tried to oversimplify and I made the statement "arrays are pointers". Though my final answer sounds pretty decent, it was only after some heavy editing in response to a lot of the comments I got.

This question is not meant to be troll bait, I understand that a pointer and an array are not the same thing, but some of the available syntax in the C++ language certainly makes them behave very similarly in a lot of cases. (FYI, my compiler is i686-apple-darwin9-g++-4.0.1 on OS X 10.5.8)

For instance, this code compiles and runs just fine for me (I realize x[8] is a potential segmentation fault):

  //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;

That particular snippet makes it look like pointers and arrays can be used almost identically, but if I add this to the bottom of that code, the last two lines won't compile:

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

So clearly the compiler at least understands that z and x are different things, but I can interchange x and y just fine.

This is further confusing when you look at passing arrays to functions and returning arrays from functions. Consider this example (again, I am aware of the potential segmentation faults here when passing 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);
}

All this code properly compiles and runs on my machine.

I feel like I have a decent internal understanding of what's going on here, but if you asked me to articulate exactly what's happening, I don't feel like I could satisfactorily explain. So here's what I'm getting at:

  • When I pass an array to a function as int* in instead of int in[], what am I gaining or losing? Is the same true when returning an array as int*? Are there ever bad side effects from doing this?

  • If I asked you what the data type of y is, would you say pointer to int, array of ints or something else?

  • Similarly, what happens when I say x = y vs. x = z? I'm still able to use x[] and access the things that were originally in y or z, but is this really just because pointer arithmetic happens to land me in memory space that is still valid?

I've dug through all the similar array/pointer questions on SO and I'm having trouble finding the definitive explanation that clears this up for me once and for all.

11
задан Community 23 May 2017 в 10:32
поделиться

8 ответов

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

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

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

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

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

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

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

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

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

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

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

[*] z = x не допускается, даже после того, как вы выполнили x = z , потому что z является (и всегда будет) конкретным массивом из 10 ints в памяти. Когда был разработан 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 , в разделе «Эмбриональный C».

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

6
ответ дан 3 December 2019 в 05:11
поделиться

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

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

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

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

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

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

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

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

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

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

5
ответ дан 3 December 2019 в 05:11
поделиться

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

  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];

Then

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

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

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

И

void func( int arr[] );

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

void func( int* arr );

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

2
ответ дан 3 December 2019 в 05:11
поделиться

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

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

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

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

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

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

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

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

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

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

1
ответ дан 3 December 2019 в 05:11
поделиться

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

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

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

6
ответ дан 3 December 2019 в 05:11
поделиться

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

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

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

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

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

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

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

Кроме того, освобождает выделенную память .

0
ответ дан 3 December 2019 в 05:11
поделиться
Другие вопросы по тегам:

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