Почему оператор printf в приведенном ниже коде печатает значение, а не мусор?

int main(){
    int array[] = [10,20,30,40,50] ;
    printf("%d\n",-2[array -2]);
    return 0 ;
}

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

4 ответа

Технически говоря, это вызывает неопределенное поведение. квотирование 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], а также -вед.

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

Технически правильный ответ заключается в том, что программа имеет неопределенное поведение, поэтому возможен любой результат, включая печать -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.


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

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

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

В 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).

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

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

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

Имея в виду array[n] эквивалентно *((array) + (n)) и вот как компилятор оценивает подписку. Это позволяет нам писать глупые запутывания, как n[array] как 100% эквивалентно array[n], Так как *((n) + (array)) эквивалентно *((array) + (n)), Как объяснено здесь:
С массивами, почему это так, что a[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,

Другие вопросы по тегам