Почему оператор printf в приведенном ниже коде печатает значение, а не мусор? - PullRequest
0 голосов
/ 14 февраля 2019
int main(){
    int array[] = [10,20,30,40,50] ;
    printf("%d\n",-2[array -2]);
    return 0 ;
}

Кто-нибудь может объяснить, как работает -2 [массив-2] и почему [] используется здесь?Это был вопрос в моем назначении, он выдает «-10», но я не понимаю, почему?

Ответы [ 4 ]

0 голосов
/ 14 февраля 2019

Вот как оценивается -2[array-2]:

Во-первых, обратите внимание, что -2[array-2] анализируется как - (2[array-2]).Подстрочный оператор [...] имеет более высокий приоритет, чем унарный оператор -.Мы часто думаем о таких константах, как -2, как об отдельных числах, но на самом деле это оператор -, применяемый к 2.

In array-2, array автоматически преобразуется в указательна свой первый элемент, поэтому он указывает на array[0].

Затем array-2 пытается вычислить указатель на два элемента до первого элемента массива.Результирующее поведение не определяется стандартом C, потому что C 2018 6.5.6 8 говорит, что определена только арифметика, которая указывает на элементы массива и конец массива.

Предположим, что мы используемРеализация C, расширяющая стандарт C путем определения указателей для использования плоского адресного пространства и разрешения произвольной арифметики указателей.Затем array-2 указывает два элемента перед массивом.

Затем 2[array-2] использует тот факт, что стандарт C определяет E1[E2] как *((E1)+(E2)).То есть оператор нижнего индекса реализуется путем добавления двух вещей и применения *.Таким образом, не имеет значения, какое выражение равно E1, а какое E2.E1+E2 совпадает с E2+E1.Так что 2[array-2] это *(2 + (array-2)).Добавление 2 перемещает указатель из двух элементов перед массивом обратно в начало массива.Затем применение * производит элемент в этом месте, а это 10.

Наконец, применение - дает −10.(Напомним, что этот вывод достигается только при использовании нашего предположения о том, что реализация C поддерживает плоское адресное пространство. Вы не можете использовать это в общем коде C).

0 голосов
/ 14 февраля 2019

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

Технически правильный ответ заключается в том, что программа имеет неопределенное поведение, поэтому любой результат возможенвключая печать -10, печать другого числа, печать чего-то другого или вообще ничего, сбой запуска, сбой и / или выполнение чего-то совершенно не связанного.

Неопределенное поведение возникает в результате оценки подвыражения array -2.array распадается из своего типа массива на указатель на первый элемент.array -2 будет указывать на элемент, который до этого стоит на две позиции, но такого элемента нет (и это не специальное правило «один за другим»), поэтому оценка этого является проблемой независимо от того, в каком контекстепоявляется в.

(C11 6.5.6 / 8 говорит)

Когда выражение, имеющее целочисленный тип, добавляется или вычитается из указателя, .... Если обаоперанд указателя и результат указывают на элементы одного и того же объекта массива или одного элемента после последнего элемента объекта массива, оценка не должна вызывать переполнение;в противном случае поведение не определено.


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

Даже если array -2вне фактического массива он оценивает некоторый адрес, который составляет 2*sizeof(int) байт перед адресом, где начинаются данные массива.Недопустимо разыменовывать этот адрес, поскольку мы не знаем, что там действительно есть int, но мы не собираемся.

Глядя на большее выражение -2[array -2], оператор []имеет более высокий приоритет, чем унарный оператор -, поэтому он означает -(2[array -2]), а не (-2)[array -2].A[B] определяется так же, как *((A)+(B)).Обычно A является значением указателя и B является целочисленным значением, но также допустимо использовать их в обратном порядке, как мы делаем здесь.Таким образом, они эквивалентны:

-2[array -2]
-(2[array -2])
-(*(2 + (array - 2)))
-(*(array))

Последний шаг действует так, как мы и ожидали: добавление двух к значению адреса array - 2 составляет 2*sizeof(int) байт после этого значения, что возвращает нас к адресупервый элемент массива.Так что *(array) разыменовывает этот адрес, давая 10, а -(*(array)) отменяет это значение, давая -10.Программа печатает -10.


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

0 голосов
/ 14 февраля 2019

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

C17 6.5.2.1 Состояния подписки массива:

Определение оператора индекса [] состоит в том, что E1[E2] идентично (*((E1)+(E2)))

Значение array[n] эквивалентно *((array) + (n)), и именно так компилятор оценивает подписку.Это позволяет нам написать глупое запутывание как n[array] как 100% эквивалентное array[n].Потому что *((n) + (array)) эквивалентно *((array) + (n)).Как объяснено здесь:
С массивами, почему [5] == 5 [a]?

Глядя на выражение -2[array -2], в частности:

  • [array -2] и [array - 2] естественно эквивалентны.В этом случае первый - просто небрежный стиль, специально используемый для того, чтобы запутать код.
  • Приоритет оператора говорит нам сначала рассмотреть [].
  • Таким образом, выражение эквивалентно -*( (2) + (array - 2) )
  • Обратите внимание, что первое - не является частью целочисленной константы 2.C не поддерживает отрицательные целочисленные константы 1) , - на самом деле является унарным оператором минус.
  • Унарный минус имеет более низкую прецедентность, чем [], поэтому 2 в -2[ «связывается» с [.
  • Подвыражение (array - 2) вычисляетсяиндивидуально и вызывает неопределенное поведение в соответствии с C17 6.5.6 / 8:

    Когда выражение с целочисленным типом добавляется или вычитается из указателя, результат имеет тип операнда-указателя./ - / Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один после последнего элемента объекта массива, при оценке не должно быть переполнения;в противном случае поведение не определено.

  • Предположительно, одной из возможных форм неопределенного поведения может быть то, что компилятор решит заменить все выражение (2) + (array - 2) на array вв этом случае все выражение в итоге получит -*array и напечатает -10.

    Гарантий на это нет, поэтому код плохой.Если вам дали задание объяснить, почему код печатает -10, ваш учитель некомпетентен.Мало того, что бессмысленно / вредно изучать запутывание как часть исследований на С, вредно полагаться на неопределенное поведение или ожидать, что оно даст определенный результат.


1) C скорее поддерживает отрицательные целочисленные константы .-2 является выражением целочисленной константы, где 2 является целочисленной константой типа int.

0 голосов
/ 14 февраля 2019

Технически говоря, это вызывает неопределенное поведение.Цитирование C11, глава §6.5.6

Если и операнд-указатель, и результат указывают на элементы одного и того же объекта массива или один после последнего элемента объекта массива, оценка должнане производить переполнение;в противном случае поведение не определено.[....]

Таким образом, (array-2) - неопределенное поведение.

Однако большинство компиляторов прочтет индексирование и, вероятно, сможет обнулить +2 и -2 индексирование, [2[a] такое же, как a[2], что совпадает с *(a+2), таким образом, 2[a-2] равно *((2)+(a-2))] и учитывает только оставшееся выражение, которое должно быть оценено, что составляет *(a) или, a[0].

Затем проверьте, что приоритет оператора

-2[array -2] фактически совпадает с -(array[0]).Итак, результатом является значение array[0] и - ved.

...