Как указывает Джоэл в подкасте № 34 «Переполнение стека» на языке программирования C (он же K & R), в C упоминается это свойство массивов:a[5] == 5[a]
Джоэл говорит, что это из-за арифметики указателей, но я все еще не понимаю. Почемуa[5] == 5[a]
?
a[1]
как серию токенов, а не строк: * ({целочисленное расположение} a {operator} + {integer} 1) совпадает с * ({целое число} 1 {operator} + {целочисленное расположение} a) но это не то же самое, что * ({целочисленное расположение} a {operator} + {operator} +)char bar[]; int foo[];
иfoo[i][bar]
используете как выражение.a[b]
=*(a + b)
для любого данногоa
иb
, но это был свободный выбор проектировщиков языка для+
определения коммутативности для всех типов. Ничто не может помешать им запретитьi + p
при разрешенииp + i
.+
что он будет коммутативным, поэтому, возможно, реальная проблема заключается в том, чтобы сделать операции указателя похожими на арифметику, вместо того, чтобы разрабатывать отдельный оператор смещения.Ответы:
Стандарт C определяет
[]
оператор следующим образом:a[b] == *(a + b)
Поэтому
a[5]
оценим:и
5[a]
будет оценивать:a
указатель на первый элемент массиваa[5]
это значение, от которого на 5 элементов дальшеa
, то же самое*(a + 5)
, и из математики начальной школы мы знаем, что они равны (сложение коммутативно ).источник
a[5]
будет скомпилирован в нечто подобноеmov eax, [ebx+20]
вместо[ebx+5]
*(10 + (int *)13) != *((int *)10 + 13)
. Другими словами, здесь происходит больше, чем арифметика в начальной школе. Коммутативность критически зависит от компилятора, который распознает, какой операнд является указателем (и какой размер объекта). Другими словами(1 apple + 2 oranges) = (2 oranges + 1 apple)
, но(1 apple + 2 oranges) != (1 orange + 2 apples)
.Потому что доступ к массиву определяется с помощью указателей.
a[i]
определяется, чтобы означать*(a + i)
, что является коммутативным.источник
*(i + a)
, что можно записать какi[a]
».*(a + i)
коммутативно». Однако,*(a + i) = *(i + a) = i[a]
потому что сложение коммутативно.Я думаю, что что-то упускается другими ответами.
Да,
p[i]
по определению эквивалентно тому*(p+i)
, что (потому что сложение коммутативно) эквивалентно тому*(i+p)
, что (опять же, по определению[]
оператора) эквивалентноi[p]
.(И в
array[i]
этом случае имя массива неявно преобразуется в указатель на первый элемент массива.)Но коммутативность сложения не так уж очевидна в этом случае.
Когда оба операнда имеют значение того же типа, или даже различных числовых типов, которые способствовали к общему типу, коммутативности имеет смысл:
x + y == y + x
.Но в данном случае речь идет именно об арифметике указателей, где один операнд является указателем, а другой - целым числом. (Целое + целое - это другая операция, а указатель + указатель - это нонсенс.)
Описание
+
оператора в стандарте C ( N1570 6.5.6) гласит:С таким же успехом можно было бы сказать:
в этом случае оба
i + p
иi[p]
будут незаконными.В терминах C ++ у нас действительно есть два набора перегруженных
+
операторов, которые можно условно описать так:а также
из которых только первое действительно необходимо.
Так почему же так?
C ++ унаследовал это определение от C, который получил его от B (коммутативность индексации массива явно упоминается в «Справочнике пользователей B» за 1972 г. ), который получил его от BCPL (руководство от 1967 г.), который вполне мог получить его даже от более ранние языки (CPL? Algol?).
Таким образом, идея о том, что индексация массива определяется с точки зрения сложения и что сложение, даже указателя и целого числа, является коммутативной, восходит на многие десятилетия к языкам-предкам Си.
Эти языки были гораздо менее типизированы, чем современные языки. В частности, различие между указателями и целыми числами часто игнорировалось. (Ранние программисты на Си иногда использовали указатели как целые числа без знака до того, как
unsigned
ключевое слово было добавлено к языку.) Поэтому идея сделать добавление некоммутативной, поскольку операнды имеют разные типы, вероятно, не возникла бы у разработчиков этих языков. Если пользователь хотел добавить две «вещи», будь то эти «вещи», являются целыми числами, указателями или чем-то еще, язык не мог предотвратить это.И на протяжении многих лет любое изменение этого правила нарушало бы существующий код (хотя стандарт ANSI C 1989 года мог бы стать хорошей возможностью).
Изменение C и / или C ++, требующее размещения указателя слева и целого числа справа, может нарушить некоторый существующий код, но при этом не будет потеря реальной выразительной силы.
Так что теперь мы имеем в виду
arr[3]
и имеем в3[arr]
виду одно и то же, хотя последняя форма никогда не должна появляться за пределами IOCCC .источник
3[arr]
это интересный артефакт, но его следует использовать редко, если когда-либо. Принятый ответ на этот вопрос (< stackoverflow.com/q/1390365/356> ), который я задал некоторое время назад, изменил мой взгляд на синтаксис. Хотя технически зачастую нет правильного и неправильного способа сделать эти вещи, такие функции заставляют вас думать не так, как о деталях реализации. Этот способ мышления имеет преимущество, которое частично теряется, когда вы зацикливаетесь на деталях реализации.ring16_t
которое содержит 65535, привело бы кring16_t
значению 1, независимому от размераint
.И, конечно же,
Основная причина этого заключалась в том, что еще в 70-х годах, когда разрабатывался C, у компьютеров не было большого количества памяти (64 КБ было много), поэтому компилятор C не делал много проверки синтаксиса. Следовательно, "
X[Y]
" был довольно слепо переведен на "*(X+Y)
"Это также объясняет синтаксис "
+=
" и "++
". Все в форме "A = B + C
" имеет одинаковую скомпилированную форму. Но если B был тем же объектом, что и A, тогда была доступна оптимизация на уровне сборки. Но компилятор не был достаточно умным, чтобы распознать его, поэтому разработчик должен был (A += C
). Точно так же, еслиC
было1
, была доступна другая оптимизация на уровне сборки, и разработчику снова пришлось сделать это явным, потому что компилятор ее не распознал. (В последнее время это делают компиляторы, поэтому эти синтаксисы сегодня в основном не нужны)источник
Похоже, никто не упомянул о проблеме Дины с
sizeof
:Вы можете добавить только целое число к указателю, вы не можете добавить два указателя вместе. Таким образом, добавляя указатель на целое число или целое число на указатель, компилятор всегда знает, какой бит имеет размер, который необходимо учитывать.
источник
Ответить на вопрос буквально. Это не всегда правда, что
x == x
печать
источник
cout << (a[5] == a[5] ? "true" : "false") << endl;
естьfalse
.x == x
это не всегда так). Я думаю, что это было его намерение. Так что он технически правильный (и, возможно, как говорится, лучший вид правильного!).NAN
в<math.h>
, который лучше0.0/0.0
, потому что0.0/0.0
это UB , когда__STDC_IEC_559__
не определена (Большинство реализаций не определяют__STDC_IEC_559__
, но в большинстве реализаций0.0/0.0
все равно будет работать)Я просто обнаружил, что этот уродливый синтаксис может быть «полезен» или, по крайней мере, очень забавно играть, когда вы хотите иметь дело с массивом индексов, которые ссылаются на позиции в том же массиве. Он может заменить вложенные квадратные скобки и сделать код более читабельным!
Конечно, я вполне уверен, что в реальном коде для этого нет смысла, но я все равно нашел это интересным :)
источник
i[a][a][a]
вы думаете, что я либо указатель на массив, либо массив указателя на массив или массив ... иa
это индекс. Когда вы видитеa[a[a[i]]]
, вы думаете, что это указатель на массив или массив иi
индекс.Хороший вопрос / ответы.
Сразу хочу отметить, что указатели и массивы C не совпадают , хотя в этом случае разница несущественна.
Рассмотрим следующие объявления:
В
a.out
, символa
находится по адресу, который является началом массива, и символp
находится по адресу, где хранится указатель, а значение указателя в этой ячейке памяти является началом массива.источник
int a[10]
был указатель с именем «a», который указывал на достаточно места для хранения 10 целых чисел в другом месте. Таким образом, a + i и j + i имели одинаковую форму: добавьте содержимое пары ячеек памяти. На самом деле, я думаю, что BCPL был без типов, поэтому они были идентичны. И масштабирование по типу не применимо, поскольку BCPL был ориентирован исключительно на слова (также на машинах с адресной адресацией).int*p = a;
сint b = 5;
В последнем, «б» и «5» являются целыми числами, а «Ь» является переменной, а «5» представляет собой фиксированное значение. Аналогично, «p» и «a» оба являются адресами символа, но «a» является фиксированным значением.Для указателей в C мы имеем
а также
Следовательно, это правда, что
a[5] == 5[a].
источник
Не ответ, а просто пища для размышлений. Если в классе перегружен оператор index / subscript, выражение
0[x]
не будет работать:Поскольку у нас нет доступа к классу int , это невозможно сделать:
источник
class Sub { public: int operator[](size_t nIndex) const { return 0; } friend int operator[](size_t nIndex, const Sub& This) { return 0; } };
operator[]
msgstr " должна быть нестатической функцией-членом с ровно одним параметром." Я был знаком с этим ограничениемoperator=
, не думал, что он применяется к[]
.[]
оператора, оно никогда не будет эквивалентным снова ... еслиa[b]
оно равно*(a + b)
и вы измените это, вам придется перегружать также,int::operator[](const Sub&);
иint
это не класс ...У него очень хорошее объяснение в «Учебнике по указателям и массивам в Си » Теда Дженсена.
Тед Дженсен объяснил это так:
источник
Я знаю, что на вопрос дан ответ, но я не удержался, чтобы поделиться этим объяснением.
Я помню Принципы разработки компилятора. Предположим,
a
этоint
массив и размерint
2 байта, а базовый адрес дляa
1000.Как
a[5]
будет работать ->Так,
Точно так же, когда код c разбит на 3-адресный код,
5[a]
станет ->Таким образом , в основном оба заявления указывают на то же место в памяти и , следовательно,
a[5] = 5[a]
.Это объяснение также является причиной, по которой отрицательные индексы в массивах работают в C.
то есть, если я получу доступ,
a[-5]
это даст мнеОн вернет мне объект в локации 990.
источник
В массивах С ,
arr[3]
и3[arr]
одни и те же, и их эквивалентные обозначения указателей являются*(arr + 3)
для*(3 + arr)
. Но наоборот[arr]3
или[3]arr
не правильно и приведет к синтаксической ошибке, так как(arr + 3)*
и(3 + arr)*
не являются допустимыми выражениями. Причина в том, что оператор разыменования должен быть помещен перед адресом, полученным выражением, а не после адреса.источник
в компиляторе c
разные способы ссылки на элемент в массиве! (Не совсем странно)
источник
Немного истории сейчас. Среди других языков BCPL оказал довольно значительное влияние на раннее развитие C. Если вы объявили массив в BCPL с чем-то вроде:
это фактически выделило 11 слов памяти, а не 10. Как правило, V было первым и содержало адрес непосредственно следующего слова. Таким образом, в отличие от C, имя V пошло в это место и взяло адрес нулевого элемента массива. Поэтому косвенность массива в BCPL, выраженная как
действительно нужно было сделать
J = !(V + 5)
(используя синтаксис BCPL), поскольку было необходимо выбрать V, чтобы получить базовый адрес массива. Таким образомV!5
и5!V
были синонимами. В качестве эпизодического наблюдения, WAFL (функциональный язык Warwick) был написан на BCPL, и, насколько я помню, имел тенденцию использовать последний синтаксис, а не первый для доступа к узлам, используемым в качестве хранилища данных. Конечно, это где-то между 35 и 40 годами назад, так что моя память немного ржавая. :)Инновация избавления от лишнего слова хранения и того, чтобы компилятор вставлял базовый адрес массива, когда он был назван, появилась позже. Согласно историческому докладу C, это произошло примерно в то время, когда структуры были добавлены в C.
Обратите внимание, что
!
в BCPL был как унарный префиксный оператор, так и бинарный инфиксный оператор, в обоих случаях выполнял косвенное обращение. просто двоичная форма включала добавление двух операндов перед выполнением косвенного обращения. Учитывая словоориентированную природу BCPL (и B), это действительно имело большой смысл. Ограничение «указатель и целое число» было сделано необходимым в C, когда он получил типы данных, иsizeof
стал чем-то особенным.источник
Ну, это особенность, которая возможна только из-за языковой поддержки.
Компилятор интерпретирует
a[i]
как*(a+i)
и выражение5[a]
вычисляет до*(5+a)
. Поскольку сложение коммутативно, оказывается, что оба равны. Следовательно, выражение оценивается какtrue
.источник
В С
Указатель является «переменной»
имя массива является "мнемоническим" или "синонимом"
p++;
действует, ноa++
недействителенa[2]
равен 2 [а], потому что внутренняя операция на обоих это«Арифметика указателя» внутренне рассчитывается как
*(a+3)
равно*(3+a)
источник
типы указателей
1) указатель на данные
2) постоянный указатель на данные
3) постоянный указатель на постоянные данные
и массивы имеют тип (2) из нашего списка.
Когда вы определяете массив одновременно, один адрес инициализируется в этом указателе.
Мы знаем, что мы не можем изменить или изменить значение const в нашей программе, потому что он выдает ОШИБКУ. при компиляции время
Основное различие я нашел ...
Мы можем повторно инициализировать указатель по адресу, но не в том же случае с массивом.
======
и вернемся к вашему вопросу ...
a[5]
это ничего, но*(a + 5)
вы можете легко понять это,
a
указав адрес (люди называют его базовым адресом), как указатель типа (2) в нашем списке[]
- этот оператор может быть заменяется указателем*
.так наконец ...
источник