В C/С++ для массива a я только что узнал, что (void *) & a == (void *) a. Как это работает?
Итак, я всегда знал, что массив "объектов", которые передаются в C/С++, просто содержит адрес первого объекта в массиве.
Как указатель на массив "объект" и он содержит значение, то же самое?
Может ли кто-нибудь указать мне на дополнительную информацию, возможно, на то, как все, что работает в сборке, возможно.
Ответы
Ответ 1
Короткий ответ: указатель на массив определяется таким же значением, как указатель на первый элемент массива. То, как работают массивы на C и С++.
Педантичный ответ:
C и С++ имеют выражения rvalue и lvalue. Lvalue - это то, к чему может применяться оператор &
. Они также имеют неявные преобразования. Перед использованием объект может быть преобразован в другой тип. (Например, если вы вызываете sqrt( 9 )
, то 9
преобразуется в double
, потому что sqrt( int )
не определен.)
lvalue типа массива неявно преобразуется в указатель. Неявное преобразование изменяет array
на &array[0]
. Это также может быть явно выписано как static_cast< int * >( array )
в С++.
Делать это нормально. Кастинг до void*
- это еще одна история. void*
немного уродлив. И кастинг с круглыми скобками как (void*)array
также уродлив. Поэтому, пожалуйста, избегайте (void*) a
в фактическом коде.
Ответ 2
Вы смешиваете две несвязанные (и, фактически, взаимоисключающие) вещи, что создает больше путаницы.
Во-первых, вы правильно заявляете, что "объекты массива, которые передаются в C/С++, просто содержат адрес первого объекта в массиве". Ключевые слова здесь "пройдены". В действительности массивы не могут быть переданы как объекты массива. Массивы не копируются. Всякий раз, когда вы используете объявление типа массива в списке параметров функции, оно фактически интерпретируется как объявление указателя, то есть это указатель, который вы "проходите", а не массив. Однако в таких ситуациях ваше равенство не выполняется
void foo(int a[]) {
assert((void *) &a == (void *) a); // FAIL!!!
}
Вышеприведенное утверждение гарантированно терпит неудачу - равенство не выполняется. Итак, в контексте этого вопроса вы должны забыть о массивах, которые вы "проходите" (по крайней мере, для синтаксиса, используемого в приведенном выше примере). Ваше равенство не выполняется для массивов, которые были заменены объектами-указателями.
Во-вторых, реальные объекты массива не являются указателями. И нет необходимости использовать термин "объект" в кавычках. Массивы - это полноценные объекты, хотя и с некоторыми своеобразными свойствами. Соответствующее равенство действительно выполняется для реальных массивов, которые не потеряли свою "массивность", т.е. Объект массива, который не был заменен объектами-указателями. Например
int a[10];
assert((void *) &a == (void *) a); // Never fails
Что это значит, так это то, что численный адрес всего массива совпадает с адресом его первого элемента. Здесь ничего необычного. Фактически, то же самое (в природе) равенство можно наблюдать с помощью типов структур в C/С++
struct S { int x; } a;
assert((void *) &a == (void *) &a.x); // Never fails
т.е. адрес всего структурного объекта совпадает с адресом его первого поля.
Ответ 3
Как указатель на массив "объект" и он содержит значение, то же самое?
Массив - это непрерывный блок памяти, в котором хранится несколько элементов.
Очевидно, что первый элемент в массиве находится на каком-то адресе.
Нет данных "между" первым элементом и началом фактического массива.
Следовательно, первый элемент имеет тот же адрес, что и массив.
Ответ 4
Прочитайте следующий поток
http://www.cplusplus.com/forum/beginner/29595/
В основном объясняется, что (&a != a)
из-за разницы в типе (поскольку &a
возвращает указатель на массив и a
на первый элемент), хотя оба они указывают на один и тот же адрес.
Поскольку вы отбрасываете их на (void*)
, сравнивается только значение адреса и считается равным, что означает ((void*) a == (void*)&a)
, как вы сказали. Это имеет смысл, поскольку адрес массива должен быть таким же, как и первые элементы.
Ответ 5
Посмотрим на эти два объявления:
int a[4];
int * b;
Оба a
и b
имеют тип, совместимый с int *
и могут, например, передаваться в качестве аргумента функции, ожидающей int *
:
void f(int * p);
f(a); // OK
f(b); // OK
В случае a
компилятор выделяет пространство для 4 значений int. Когда вы используете имя a
, например, при вызове f(a)
, компилятор просто заменяет адрес того, где он выделил первое из этих значений int, поскольку он знает.
В случае b
компилятор выделяет пространство для одного указателя. Когда вы используете имя b
, например, при вызове f(b)
, компилятор генерирует код для извлечения значения указателя из выделенного хранилища.
Когда дело доходит до &
, это становится очевидным, когда разница между a
и b
становится очевидной. &
всегда означает адрес хранилища, который компилятор выделил для вашей переменной: &a
- это адрес этих четырех значений int (поэтому совпадает с просто a
), а &b
- это адрес значения указателя, У них тоже разные типы.
&a
не совсем то же самое, что и a
, хотя они сравниваются как равные. Они имеют другой тип: &a
- это указатель, а a
- массив. Вы можете заметить разницу, например, если вы примените оператор sizeof
к этим выражениям: sizeof(a)
будет оценивать размер четырех значений int, а sizeof(&a)
- размер указателя.
Ответ 6
Хорошо, так что я думал, что когда вы создали массив, вы выделили место для массива где-то, и вы создали указатель на его первый объект где-то еще, и то, что вы передали в своем коде, было указателем.
На самом деле это поведение того, что происходит, когда вы создаете массив с новым в С++ или с malloc в C/С++. Таким образом,
int * a = new a[SIZE];
assert((void*)&a==(void*)a); // Always fails
Что я узнал, так это то, что для массивов, объявленных в стиле int a[SIZE];
, при попытке передать массив функции (это называется распадом-указателем-матрицей) создается указатель на первый элемент. Интересно отметить, что, как пишет AndreyT,
void foo(int a[]) {
assert((void *) &a == (void *) a); // Always fails
}
Это показывает, что только при попытке передать массивы вокруг этого указателя создается для массивов в стиле int a[SIZE];
.