2012-01-18 4 views
20

Я программировал c/C++ в течение многих лет, но сегодняшнее случайное открытие заставило меня несколько любопытно ... Почему оба выхода дают одинаковый результат в коде ниже? (arr, конечно, адрес arr[0], то есть указатель на arr[0] я ожидал бы &arr быть адрес этого указателя, но он имеет такое же значение, как arr.)Почему arr и & arr то же самое?

int arr[3]; 
    cout << arr << endl; 
    cout << &arr << endl; 

Примечание: Этот вопрос был закрыт, но теперь он снова открывается. (Спасибо?)

Я знаю, что &arr[0] и arr оценивает и тот же номер, но это не мой вопрос! Вопрос в том, почему &arr и arr оценивает одно и то же число. Если arr является литералом (не хранится в любом случае), тогда компилятор должен жаловаться и сказать, что arr не является значением lvalue. Если адрес arr хранится где-то, то &arr должен указать мне адрес этого места. (Но это не так)

если я пишу

константный Int * arr2 = обр;

arr2[i]==arr[i] тогда для любого целого i, но .

+0

Что означает «адрес этого указателя»? Указатель не сохраняется в переменной. –

+1

@David Schwartz, Если бы я написал int * arr2 = new int [3], то & arr2 было бы сохранено в переменной. Может быть, arr - буквальная константа? Но если взять адрес константы (например, & 123), то компилятор жалуется (123 не является lvalue). Компилятор не жаловался, когда я взял адрес arr. – ragnarius

+6

Я не думаю, что это дубликат! – ragnarius

ответ

13

Это не то же самое. Они просто находятся в одном месте памяти. Например, вы можете написать arr+2, чтобы получить адрес arr[2], но не (&arr)+2, чтобы сделать то же самое.

Также, sizeof arr и sizeof &arr различны.

+4

Приятно отметить, что '(& arr) + 2' будет компилироваться и, возможно, даже работать, но это неопределенное поведение и не даст вам' arr [2] '. –

+0

Он не компилируется здесь! 'can not convert 'int (*) [3]' to 'int *' в инициализации' –

+1

' int (*) [3] 'преобразуется в' int ** 'Указатель на массив можно преобразовать в указатель на указатель. –

5

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

Значение arr имеет тип int *, а значение &arr имеет тип int (*)[3].

& - адресный оператор, и адрес объекта является указателем на этот объект. Указатель на объект типа int [3] имеет тип int (*)[3]

+0

Как это бывает, адрес самого массива и адрес его первого элемента численно одинаковы. Но они имеют разные типы и ведут себя по-другому в других отношениях. –

+0

Значение 'arr' имеет тип' int (&) [3] ', который получает« повышенный »до' int * ', поскольку' cout' не имеет перегрузки для массивов. –

17
#include <cassert> 

struct foo { 
    int x; 
    int y; 
}; 

int main() {  
    foo f; 
    void* a = &f.x; 
    void* b = &f; 
    assert(a == b); 
} 

По той же причине, что два адреса a и b выше are the same. Адрес объекта совпадает с адресом его первого члена (их типы, однако, различны).

      arr 
         _______^_______ 
        /    \ 
        | [0] [1] [2] | 
--------------------+-----+-----+-----+-------------------------- 
     some memory |  |  |  |  more memory 
--------------------+-----+-----+-----+-------------------------- 
        ^
        | 
      the pointers point here 

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

+0

Я попробовал, но & f.x! = & F.y. Таким образом, адрес объекта совпадает с адресом его FIRST-члена ... – ragnarius

+1

@ragnarius: адрес массива совпадает с адресом первого элемента массива. Не имеет ничего общего с элементами элементов. –

+0

@ragnarius: это именно то, что я сказал. –

6

Оба имеют одинаковое значение, но разные типы.

Когда он используется сам по себе (не операнд & или sizeof), arr принимает значение указателя на int держа адрес первого int в массиве. &arr оценивает указатель на массив из трех int s, удерживая адрес массива. Поскольку первый int в массиве должен находиться в самом начале массива, то адреса должны быть равны.

Разница между ними становится очевидной, если сделать некоторую математику на результатах:

arr+1 будет равен arr + sizeof(int).

((&arr) + 1) будет равен arr + sizeof(arr) == arr + sizeof(int) * 3

Edit: О том, как/почему это происходит, ответ довольно прост: потому, что стандарт говорит так. В частности, он говорит (§6.3.2.1/3):

исключением случаев, когда это операнд оператора SIZEOF или одинарным & оператора, или является строка символов используется для инициализации массива, выражение который имеет тип '' array of type '', преобразуется в выражение с указателем типа '' to type '', который указывает на начальный элемент объекта массива и не является lvalue.

[примечание: эта конкретная цитата из стандарта C99, но я считаю, что существует эквивалентный язык во всех версиях стандартов C и C++].

В первом случае (arr сама по себе), arr не используется в качестве операнда SizeOf, Унарный & и т.д., поэтому он преобразуется (не раскручен) к типу «указатель на тип» (в этом case, "указатель на int").

Во втором случае (&arr), название, очевидно, является используется как операнд одноместной & оператора - так что преобразование делает не имеют место.

+0

@MooingDuck: Где вы (думаете, что видите) что-то похожее на адрес указателя? (намек: здесь нет такой вещи). Здесь не участвует никакая реклама. –

+0

вы утверждаете, что 'arr'" дает "указатель на' int', который неоднозначен и, возможно, вводит в заблуждение. –

+0

Я не уверен, что понимаю вашу цитату.Мы обсуждаем оператор унарного оператора, и ваша цитата говорит, что «Кроме того, что это [...] унарный оператор ...». – ragnarius

2

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

4

Это не то же самое.

Немного более строгое объяснение:

arr является именующего типа int [3]. Попытка использования в некоторых выражениях, таких как cout << arr, приведет к преобразованию lvalue-to-rvalue, которое, так как нет rvalues ​​типа массива, преобразует его в rvalue типа int * и со значением, равным &arr[0]. Это то, что вы можете отобразить.

&arr является Rvalue типа int (*)[3], указывая на сам объект массива. Здесь нет волшебства :-) Этот указатель указывает на тот же адрес, что и &arr[0], потому что объект массива и его первый член начинаются в том же месте в памяти. Вот почему у вас есть тот же результат при их печати.


простой способ, чтобы подтвердить, что они отличаются в сравнении *(arr) и *(&arr): первый представляет собой объект типа int и второй является объект типа int[3].