малоизвестно: унарный оператор плюс можно использовать как «оператор затухания»: если задан int a[10]; int b(void);, то +aэто указатель на int и указатель +bна функцию. Полезно, если вы хотите передать его шаблону, принимающему ссылку.
Йоханнес Шауб - лит
3
@litb - parens будет делать то же самое (например, (a) должно быть выражением, которое оценивает указатель), верно?
Майкл Берр
21
std::decayиз C ++ 14 будет менее неясным способом распада массива по сравнению с унарным +.
legends2k
21
@ JohannesSchaub-litb, поскольку этот вопрос помечен как C, так и C ++, я хотел бы уточнить, что хотя +aи +bдопустим в C ++, он недопустим в C (C11 6.5.3.3/1 "Операнд унарного +или -оператора должен иметь арифметический тип ")
ММ
5
@ Право. Но я полагаю, что это не так мало известно, как трюк с унарным +. Причина, по которой я это упомянул, была не просто потому, что она разлагается, а потому, что с ней интересно играть;)
Йоханнес Шауб - litb
Ответы:
283
Говорят, что массивы "распадаются" на указатели. Массив C ++, объявленный как, int numbers [5]не может быть перенаправлен, то есть вы не можете сказать numbers = 0x5a5aff23. Что еще более важно термин распад означает потерю типа и размерности; numbersраспадаются int*, теряя информацию о размерах (количество 5), и тип больше не int [5]существует. Ищите здесь случаи, когда распад не происходит .
Если вы передаете массив по значению, вы действительно копируете указатель - указатель на первый элемент массива копируется в параметр (тип которого также должен быть указателем на тип элемента массива). Это работает из-за разлагающейся природы массива; после распада sizeofбольше не дает полный размер массива, потому что он по сути становится указателем. Вот почему предпочтительно (среди прочих причин) передавать по ссылке или по указателю.
Три способа передачи в массиве 1 :
void by_value(const T* array)// const T array[] means the samevoid by_pointer(const T (*array)[U])void by_reference(const T (&array)[U])
Последние два дадут правильную sizeofинформацию, в то время как первый не даст, так как аргумент массива исчез, чтобы быть назначенным параметру.
1 Константа U должна быть известна во время компиляции.
by_value передает указатель на первый элемент массива; в контексте параметров функции, T a[]идентично T *a. by_pointer передает то же самое, за исключением того, что значение указателя теперь квалифицировано const. Если вы хотите , чтобы передать указатель на массив (в отличие от указателя на первый элемент массива), синтаксис T (*array)[U].
Джон Боде
4
«с явным указателем на этот массив» - это неверно. Если aэто массив char, то aимеет тип char[N], и будет распадаться на char*; но &aэто тип char(*)[N], и не будет разлагаться.
Павел Минаев
5
@FredOverflow: Так что, если Uизменения не нужно помнить, чтобы изменить его в двух местах, или рискуйте молчать об ошибках ... Автономность!
Гонки легкости на орбите
4
«Если вы передаете массив по значению, то вы действительно копируете указатель». Это не имеет смысла, потому что массивы не могут быть переданы по значению, точка.
Juanchopanza
103
Массивы в основном такие же, как указатели в C / C ++, но не совсем. Как только вы конвертируете массив:
constint a[]={2,3,5,7,11};
в указатель (который работает без приведения и, следовательно, в некоторых случаях может произойти неожиданно):
constint* p = a;
вы теряете способность sizeofоператора считать элементы в массиве:
assert(sizeof(p)!=sizeof(a));// sizes are not equal
Массивы в основном не совпадают с указателями; они совершенно разные животные. В большинстве случаев массив можно рассматривать как указатель, а указатель можно рассматривать как массив, но это настолько близко, насколько они получают.
Джон Боде
20
@ Джон, прошу прощения за мой неточный язык. Я пытался найти ответ, не увязнув в длительной предыстории, и «в основном ... но не совсем» - такое же хорошее объяснение, как я когда-либо получал в колледже. Я уверен, что любой заинтересованный может получить более точную картину из вашего комментария.
системная пауза
«работает без приведения» означает то же самое, что «происходит неявно», когда речь идет о преобразованиях типов
ММ,
47
Вот что говорит стандарт (C99 6.3.2.1/3 - Другие операнды - L-значения, массивы и обозначения функций):
За исключением случаев, когда он является операндом оператора sizeof или унарного оператора &, или является строковым литералом, используемым для инициализации массива, выражение с типом '' массив типа '' преобразуется в выражение с указателем типа '' на тип '', который указывает на начальный элемент объекта массива и не является lvalue.
Это означает, что почти всегда, когда имя массива используется в выражении, оно автоматически преобразуется в указатель на первый элемент массива.
Обратите внимание, что имена функций действуют аналогичным образом, но указатели на функции используются гораздо реже и гораздо более специализированным образом, что не вызывает такой большой путаницы, как автоматическое преобразование имен массивов в указатели.
Стандарт C ++ (4.2 преобразование массива в указатель) ослабляет требование преобразования к (выделение мое):
Значение l или значение типа «массив NT» или «массив неизвестных границ T» может быть преобразовано в значение типа «указатель на T».
Таким образом, преобразование не должно происходить, как это обычно происходит в C (это позволяет перегрузить функции или шаблоны соответствуют типу массива).
Вот почему в C вы должны избегать использования параметров массива в прототипах / определениях функций (на мой взгляд - я не уверен, есть ли общее согласие). Они вызывают путаницу и в любом случае являются фикцией - используйте параметры указателя, и путаница может не исчезнуть полностью, но, по крайней мере, объявление параметра не лжёт.
Что такое пример строки кода, где «выражение с типом« массив типа »» является «строковым литералом, используемым для инициализации массива»?
Гарретт
4
@ Гарретт char x[] = "Hello";. Массив из 6 элементов "Hello"не гниет; вместо этого xполучает размер 6и его элементы инициализируются из элементов "Hello".
ММ
30
«Распад» относится к неявному преобразованию выражения из типа массива в тип указателя. В большинстве случаев, когда компилятор видит выражение массива, он преобразует тип выражения из «массива N-элемента T» в «указатель на T» и устанавливает значение выражения в адрес первого элемента массива. , Исключения из этого правила , когда массив является операндом либо с sizeofили &операторами, или массив строкового литерала используются в качестве инициализатора в объявлении.
Предположим, следующий код:
char a[80];
strcpy(a,"This is a test");
Выражение aимеет тип «массив из 80 элементов char», а выражение «Это тест» имеет тип «массив из 16 элементов char» (в C; в C ++ строковые литералы являются массивами const char). Однако в вызове strcpy()ни одно из выражений не является операндом sizeofили &, поэтому их типы неявно преобразуются в «указатель на символ», а их значения устанавливаются по адресу первого элемента в каждом. То, что strcpy()получает, это не массивы, а указатели, как видно из его прототипа:
char*strcpy(char*dest,constchar*src);
Это не то же самое, что указатель массива. Например:
Оба ptr_to_first_elementи ptr_to_arrayимеют одинаковое значение ; Базовый адрес. Однако они относятся к разным типам и обрабатываются по-разному, как показано ниже:
Помните, что выражение a[i]интерпретируется как *(a+i)(которое работает только в том случае, если тип массива преобразуется в тип указателя), поэтому оба a[i]и ptr_to_first_element[i]работают одинаково. Выражение (*ptr_to_array)[i]интерпретируется как *(*a+i). Выражения *ptr_to_array[i]и ptr_to_array[i]могут привести к предупреждениям или ошибкам компилятора в зависимости от контекста; они определенно поступят неправильно, если вы ожидаете от них оценки a[i].
sizeof a ==sizeof*ptr_to_array ==80
Опять же, когда массив является операндом sizeof, он не преобразуется в тип указателя.
sizeof*ptr_to_first_element ==sizeof(char)==1sizeof ptr_to_first_element ==sizeof(char*)== whatever the pointer size
is on your platform
Не так "This is a test" is of type "16-element array of char"ли "15-element array of char"? (длина 14 + 1 для \ 0)
chux - Восстановить Монику
16
Массивы в Си не имеют значения.
Везде, где ожидается значение объекта, но объект является массивом, вместо него используется адрес его первого элемента с типом pointer to (type of array elements).
В функции все параметры передаются по значению (массивы не являются исключением). Когда вы передаете массив в функцию, он «распадается на указатель» (sic); когда вы сравниваете массив с чем-то другим, он снова «превращается в указатель» (sic); ...
void foo(int arr[]);
Функция foo ожидает значение массива. Но в Си массивы не имеют значения! Таким образом, fooвместо этого получается адрес первого элемента массива.
int arr[5];int*ip =&(arr[1]);if(arr == ip){/* something; */}
В приведенном выше сравнении arrне имеет значения, поэтому становится указателем. Это становится указателем на int. Этот указатель можно сравнить с переменной ip.
В синтаксисе индексации массива, который вы привыкли видеть, опять же, arr 'распадается на указатель'
arr[42];/* same as *(arr + 42); *//* same as *(&(arr[0]) + 42); */
Единственный раз, когда массив не распадается на указатель, это когда он является операндом оператора sizeof или оператора & (оператор 'address of'), или как строковый литерал, используемый для инициализации массива символов.
«Массивы не имеют значения» - что это должно означать? Конечно, массивы имеют значение ... это объекты, вы можете иметь указатели и, в C ++, ссылки на них и т. Д.
Павел Минаев
2
Я считаю, что строгое «Значение» определяется в С как интерпретация битов объекта в соответствии с типом. Мне трудно понять, как это можно понять с помощью типа массива. Вместо этого вы можете сказать, что вы конвертируете в указатель, но он не интерпретирует содержимое массива, он просто получает его местоположение. То, что вы получите, это значение указателя (и это адрес), а не значение массива (это будет «последовательность значений содержащихся элементов», как используется в определении «строка»). Тем не менее, я думаю, что было бы справедливо сказать «значение массива», когда один означает, что указатель получен.
Йоханнес Шауб - лит
во всяком случае, я думаю, что есть небольшая двусмысленность: значение объекта и значение выражения (как в «rvalue»). Если интерпретировать последний способ, то выражение массива, безусловно, имеет значение: оно является результатом его преобразования в значение r и является указателем. Но если интерпретировать первый способ, то, конечно, нет никакого полезного значения для объекта массива.
Йоханнес Шауб - лит
1
+1 за фразу с небольшим исправлением; для массивов это даже не триплет, а просто куплет [местоположение, тип]. Вы имели в виду что-то еще для третьего местоположения в случае массива? Я не могу думать ни о чем.
legends2k
1
@ legends2k: Я думаю, что я использовал третье местоположение в массивах, чтобы не делать их частным случаем только с куплетом. Может быть, [местоположение, тип, пустота ] было бы лучше.
pmg
8
Это когда массив гниет и на него указывают ;-)
На самом деле, просто если вы хотите передать массив куда-нибудь, но вместо этого передается указатель (потому что, черт возьми, он передаст весь массив за вас), люди говорят, что плохой массив распался на указатель.
Красиво сказано. Какой будет хороший массив, который не распадается на указатель или тот, который не может распадаться? Можете ли вы привести пример в C? Спасибо.
Unheilig
@Unheilig, конечно, можно упаковать массив в struct и передать структуру.
Майкл Крелин - хакер
Я не уверен, что вы подразумеваете под "работой". Ему не разрешен доступ за массивом, хотя он работает, как и ожидалось, если вы ожидаете, что на самом деле произойдет. Такое поведение (хотя, опять же, официально не определено) сохраняется.
Майкл Крелин - хакер
Распад также происходит во многих ситуациях, которые нигде не пропускают массив (как описано в других ответах). Например, a + 1.
ММ
3
Затухание массива означает, что когда массив передается в качестве параметра функции, он обрабатывается идентично ("распадается на") указателю.
void do_something(int*array){// We don't know how big array is here, because it's decayed to a pointer.
printf("%i\n",sizeof(array));// always prints 4 on a 32-bit machine}int main (int argc,char**argv){int a[10];int b[20];int*c;
printf("%zu\n",sizeof(a));//prints 40 on a 32-bit machine
printf("%zu\n",sizeof(b));//prints 80 on a 32-bit machine
printf("%zu\n",sizeof(c));//prints 4 on a 32-bit machine
do_something(a);
do_something(b);
do_something(c);}
Есть два осложнения или исключения из вышеперечисленного.
Во-первых, при работе с многомерными массивами в C и C ++ теряется только первое измерение. Это связано с тем, что массивы расположены в памяти непрерывно, поэтому компилятор должен знать все, кроме первого измерения, чтобы иметь возможность вычислять смещения в этом блоке памяти.
void do_something(int array[][10]){// We don't know how big the first dimension is.}int main(int argc,char*argv[]){int a[5][10];int b[20][10];
do_something(a);
do_something(b);return0;}
Во-вторых, в C ++ вы можете использовать шаблоны для определения размера массивов. Microsoft использует это для версий Secure CRT на C ++, таких как strcpy_s , и вы можете использовать аналогичный прием для надежного получения количества элементов в массиве .
Я мог бы быть настолько смелым, чтобы думать, что есть четыре (4) способа передать массив в качестве аргумента функции. Также вот короткий, но рабочий код для вашего прочтения.
#include<iostream>#include<string>#include<vector>#include<cassert>usingnamespace std;// test data// notice native array init with no copy aka "="// not possible in Cconstchar* specimen[]{ __TIME__, __DATE__, __TIMESTAMP__ };// ONE// simple, dangerous and uselesstemplate<typename T>void as_pointer(const T*array){// a pointer
assert(array!=nullptr);};// TWO// for above const T array[] means the same// but and also , minimum array size indication might be given too// this also does not stop the array decay into T *// thus size information is losttemplate<typename T>void by_value_no_size(const T array[0xFF]){// decayed to a pointer
assert(array!=nullptr);}// THREE// size information is preserved// but pointer is asked fortemplate<typename T,size_t N>void pointer_to_array(const T (*array)[N]){// dealing with native pointer
assert(array!=nullptr);}// FOUR// no C equivalent// array by reference// size is preservedtemplate<typename T,size_t N>void reference_to_array(const T (&array)[N]){// array is not a pointer here// it is (almost) a container// most of the std:: lib algorithms // do work on array reference, for example// range for requires std::begin() and std::end()// on the type passed as range to iterate overfor(auto&& elem :array){
cout << endl << elem ;}}int main(){// ONE
as_pointer(specimen);// TWO
by_value_no_size(specimen);// THREE
pointer_to_array(&specimen);// FOUR
reference_to_array( specimen );}
Я мог бы также подумать, что это показывает превосходство C ++ над C. По крайней мере, в отношении ссылок (каламбур) передачи массива по ссылке.
Конечно, есть очень строгие проекты без выделения кучи, без исключений и без std :: lib. Можно сказать, что нативная обработка массива в C ++ является критически важной функцией языка.
int a[10]; int b(void);
, то+a
это указатель на int и указатель+b
на функцию. Полезно, если вы хотите передать его шаблону, принимающему ссылку.std::decay
из C ++ 14 будет менее неясным способом распада массива по сравнению с унарным +.+a
и+b
допустим в C ++, он недопустим в C (C11 6.5.3.3/1 "Операнд унарного+
или-
оператора должен иметь арифметический тип ")Ответы:
Говорят, что массивы "распадаются" на указатели. Массив C ++, объявленный как,
int numbers [5]
не может быть перенаправлен, то есть вы не можете сказатьnumbers = 0x5a5aff23
. Что еще более важно термин распад означает потерю типа и размерности;numbers
распадаютсяint*
, теряя информацию о размерах (количество 5), и тип больше неint [5]
существует. Ищите здесь случаи, когда распад не происходит .Если вы передаете массив по значению, вы действительно копируете указатель - указатель на первый элемент массива копируется в параметр (тип которого также должен быть указателем на тип элемента массива). Это работает из-за разлагающейся природы массива; после распада
sizeof
больше не дает полный размер массива, потому что он по сути становится указателем. Вот почему предпочтительно (среди прочих причин) передавать по ссылке или по указателю.Три способа передачи в массиве 1 :
Последние два дадут правильную
sizeof
информацию, в то время как первый не даст, так как аргумент массива исчез, чтобы быть назначенным параметру.1 Константа U должна быть известна во время компиляции.
источник
T a[]
идентичноT *a
. by_pointer передает то же самое, за исключением того, что значение указателя теперь квалифицированоconst
. Если вы хотите , чтобы передать указатель на массив (в отличие от указателя на первый элемент массива), синтаксисT (*array)[U]
.a
это массивchar
, тоa
имеет типchar[N]
, и будет распадаться наchar*
; но&a
это типchar(*)[N]
, и не будет разлагаться.U
изменения не нужно помнить, чтобы изменить его в двух местах, или рискуйте молчать об ошибках ... Автономность!Массивы в основном такие же, как указатели в C / C ++, но не совсем. Как только вы конвертируете массив:
в указатель (который работает без приведения и, следовательно, в некоторых случаях может произойти неожиданно):
вы теряете способность
sizeof
оператора считать элементы в массиве:Эта потерянная способность называется «распадом».
Для более подробной информации, ознакомьтесь с этой статьей о распаде массива .
источник
Вот что говорит стандарт (C99 6.3.2.1/3 - Другие операнды - L-значения, массивы и обозначения функций):
Это означает, что почти всегда, когда имя массива используется в выражении, оно автоматически преобразуется в указатель на первый элемент массива.
Обратите внимание, что имена функций действуют аналогичным образом, но указатели на функции используются гораздо реже и гораздо более специализированным образом, что не вызывает такой большой путаницы, как автоматическое преобразование имен массивов в указатели.
Стандарт C ++ (4.2 преобразование массива в указатель) ослабляет требование преобразования к (выделение мое):
Таким образом, преобразование не должно происходить, как это обычно происходит в C (это позволяет перегрузить функции или шаблоны соответствуют типу массива).
Вот почему в C вы должны избегать использования параметров массива в прототипах / определениях функций (на мой взгляд - я не уверен, есть ли общее согласие). Они вызывают путаницу и в любом случае являются фикцией - используйте параметры указателя, и путаница может не исчезнуть полностью, но, по крайней мере, объявление параметра не лжёт.
источник
char x[] = "Hello";
. Массив из 6 элементов"Hello"
не гниет; вместо этогоx
получает размер6
и его элементы инициализируются из элементов"Hello"
.«Распад» относится к неявному преобразованию выражения из типа массива в тип указателя. В большинстве случаев, когда компилятор видит выражение массива, он преобразует тип выражения из «массива N-элемента T» в «указатель на T» и устанавливает значение выражения в адрес первого элемента массива. , Исключения из этого правила , когда массив является операндом либо с
sizeof
или&
операторами, или массив строкового литерала используются в качестве инициализатора в объявлении.Предположим, следующий код:
Выражение
a
имеет тип «массив из 80 элементов char», а выражение «Это тест» имеет тип «массив из 16 элементов char» (в C; в C ++ строковые литералы являются массивами const char). Однако в вызовеstrcpy()
ни одно из выражений не является операндомsizeof
или&
, поэтому их типы неявно преобразуются в «указатель на символ», а их значения устанавливаются по адресу первого элемента в каждом. То, чтоstrcpy()
получает, это не массивы, а указатели, как видно из его прототипа:Это не то же самое, что указатель массива. Например:
Оба
ptr_to_first_element
иptr_to_array
имеют одинаковое значение ; Базовый адрес. Однако они относятся к разным типам и обрабатываются по-разному, как показано ниже:Помните, что выражение
a[i]
интерпретируется как*(a+i)
(которое работает только в том случае, если тип массива преобразуется в тип указателя), поэтому обаa[i]
иptr_to_first_element[i]
работают одинаково. Выражение(*ptr_to_array)[i]
интерпретируется как*(*a+i)
. Выражения*ptr_to_array[i]
иptr_to_array[i]
могут привести к предупреждениям или ошибкам компилятора в зависимости от контекста; они определенно поступят неправильно, если вы ожидаете от них оценкиa[i]
.Опять же, когда массив является операндом
sizeof
, он не преобразуется в тип указателя.ptr_to_first_element
простой указатель на символисточник
"This is a test" is of type "16-element array of char"
ли"15-element array of char"
? (длина 14 + 1 для \ 0)Массивы в Си не имеют значения.
Везде, где ожидается значение объекта, но объект является массивом, вместо него используется адрес его первого элемента с типом
pointer to (type of array elements)
.В функции все параметры передаются по значению (массивы не являются исключением). Когда вы передаете массив в функцию, он «распадается на указатель» (sic); когда вы сравниваете массив с чем-то другим, он снова «превращается в указатель» (sic); ...
Функция foo ожидает значение массива. Но в Си массивы не имеют значения! Таким образом,
foo
вместо этого получается адрес первого элемента массива.В приведенном выше сравнении
arr
не имеет значения, поэтому становится указателем. Это становится указателем на int. Этот указатель можно сравнить с переменнойip
.В синтаксисе индексации массива, который вы привыкли видеть, опять же, arr 'распадается на указатель'
Единственный раз, когда массив не распадается на указатель, это когда он является операндом оператора sizeof или оператора & (оператор 'address of'), или как строковый литерал, используемый для инициализации массива символов.
источник
Это когда массив гниет и на него указывают ;-)
На самом деле, просто если вы хотите передать массив куда-нибудь, но вместо этого передается указатель (потому что, черт возьми, он передаст весь массив за вас), люди говорят, что плохой массив распался на указатель.
источник
a + 1
.Затухание массива означает, что когда массив передается в качестве параметра функции, он обрабатывается идентично ("распадается на") указателю.
Есть два осложнения или исключения из вышеперечисленного.
Во-первых, при работе с многомерными массивами в C и C ++ теряется только первое измерение. Это связано с тем, что массивы расположены в памяти непрерывно, поэтому компилятор должен знать все, кроме первого измерения, чтобы иметь возможность вычислять смещения в этом блоке памяти.
Во-вторых, в C ++ вы можете использовать шаблоны для определения размера массивов. Microsoft использует это для версий Secure CRT на C ++, таких как strcpy_s , и вы можете использовать аналогичный прием для надежного получения количества элементов в массиве .
источник
tl; dr: когда вы используете определенный вами массив, вы фактически будете использовать указатель на его первый элемент.
Таким образом:
arr[idx]
, ты на самом деле просто говоришь*(arr + idx)
.Сортировка исключений из этого правила:
struct
.sizeof()
дает размер, занятый массивом, а не размер указателя.источник
Я мог бы быть настолько смелым, чтобы думать, что есть четыре (4) способа передать массив в качестве аргумента функции. Также вот короткий, но рабочий код для вашего прочтения.
Я мог бы также подумать, что это показывает превосходство C ++ над C. По крайней мере, в отношении ссылок (каламбур) передачи массива по ссылке.
Конечно, есть очень строгие проекты без выделения кучи, без исключений и без std :: lib. Можно сказать, что нативная обработка массива в C ++ является критически важной функцией языка.
источник