Невозможно понять этот способ, чтобы вычислить квадрат числа

Я нашел функцию, которая вычисляет квадрат числа:

int p(int n) {
    int a[n]; //works on C99 and above
    return (&a)[n] - a;
}

Возвращает значение n 2. Вопрос в том, как это делается? После небольшого тестирования я обнаружил, что между (&a)[k] и (&a)[k+1] есть sizeof(a)/sizeof(int). Почему это?

Ответ 1

Очевидно, взлом... но способ возведения в квадрат числа без использования оператора * (это было требование конкурса).

(&a)[n] 

эквивалентен указателю на int в местоположении

(a + sizeof(a[n])*n)

и, следовательно, все выражение

  (&a)[n] -a 

= (a + sizeof(a[n])*n -a) /sizeof(int)

= sizeof(a[n])*n / sizeof(int)
= sizeof(int) * n * n / sizeof(int)
= n * n

Ответ 2

Чтобы понять этот хак, сначала вам нужно понять разницу указателей, то есть, что произойдет, когда два указателя, указывающие на элементы одного массива, будут вычтены?

Когда один указатель вычитается из другого, результатом является расстояние (измеренное в элементах массива) между указателями. Итак, если p указывает на a[i] и q указывает на a[j], то p - q равно i - j.

C11: 6.5.6 Аддитивные операторы (p9):

Когда два указателя вычитаются, оба должны указывать на элементы одного и того же объекта массива или один за последним элементом объекта массива; результат - это разность индексов двух элементов массива. [...].
Другими словами, если выражения p и q указывают соответственно на i -th и j -th элементы объекта массива, выражение (P)-(Q) имеет значение i−j, если значение соответствует объекту типа ptrdiff_t.

Теперь я ожидаю, что вам известно о преобразовании имени массива в указатель, a преобразуется в указатель на первый элемент массива a. &a - адрес всего блока памяти, т.е. это адрес массива a. Приведенный ниже рисунок поможет вам понять (прочитать этот ответ для подробного объяснения):

enter image description here

Это поможет вам понять, почему a и &a имеют один и тот же адрес и как (&a)[i] - это адрес массива я th (того же размера, что и для a).

Итак, утверждение

return (&a)[n] - a; 

эквивалентно

return (&a)[n] - (&a)[0];  

и это различие даст количество элементов между указателями (&a)[n] и (&a)[0], которые n массивы каждого из элементов n int. Следовательно, общие элементы массива n*n= n 2.


ПРИМЕЧАНИЕ.

C11: 6.5.6 Аддитивные операторы (p9):

Когда два указателя вычитаются, оба указывают на элементы одного и того же объекта массива, или один за последним элементом объекта массива; в результате возникает разница индексы двух элементов массива. Размер результата определяется реализацией, и его тип (целочисленный тип со знаком) равен ptrdiff_t, определенному в заголовке <stddef.h>. Если результат не представлен в объекте такого типа, поведение undefined.

Так как (&a)[n] не указывает на элементы одного и того же объекта массива или один за последним элементом объекта массива, (&a)[n] - a будет вызывать поведение undefined.

Также обратите внимание, что лучше изменить возвращаемый тип функции p на ptrdiff_t.

Ответ 3

a является (переменным) массивом n int.

&a является указателем на (переменный) массив n int.

(&a)[1] является указателем int one int за последним элементом массива. Этот указатель содержит n int элементы после &a[0].

(&a)[2] является указателем int one int за последним элементом массива из двух массивов. Этот указатель содержит 2 * n int элементы после &a[0].

(&a)[n] является указателем int one int за последним элементом массива массива n. Этот указатель имеет n * n int элементы после &a[0]. Просто вычтите &a[0] или a, и у вас есть n.

Конечно, это технически undefined поведение, даже если оно работает на вашем компьютере, поскольку (&a)[n] не указывает внутри массива или мимо последнего элемента массива (как это требуется правилами C для арифметики указателя).

Ответ 4

Если у вас есть два указателя, которые указывают на два элемента одного и того же массива, тогда его различие даст количество элементов между этими указателями. Например, этот фрагмент кода будет выводить 2.

int a[10];

int *p1 = &a[1];
int *p2 = &a[3];

printf( "%d\n", p2 - p1 ); 

Теперь рассмотрим выражение

(&a)[n] - a;

В этом выражении a имеет тип int * и указывает на его первый элемент.

Выражение &a имеет тип int ( * )[n] и указывает на первую строку отображаемого двумерного массива. Его значение соответствует значению a, но типы разные.

( &a )[n]

является n-м элементом этого двумерного массива с изображением и имеет тип int[n] То есть это n-я строка отображаемого массива. В выражении (&a)[n] - a он преобразуется в адрес его первого элемента и имеет тип `int *.

Таким образом, между (&a)[n] и a существует n строк из n элементов. Таким образом, разность будет равна n * n.

Ответ 5

Expression     | Value                | Explanation
a              | a                    | point to array of int elements
a[n]           | a + n*sizeof(int)    | refer to n-th element in array of int elements
-------------------------------------------------------------------------------------------------
&a             | a                    | point to array of (n int elements array)
(&a)[n]        | a + n*sizeof(int[n]) | refer to n-th element in array of (n int elements array)
-------------------------------------------------------------------------------------------------
sizeof(int[n]) | n * sizeof(int)      | int[n] is a type of n-int-element array

Таким образом,

  • Тип (&a)[n] - указатель int[n]
  • Тип a - указатель int

Теперь выражение (&a)[n]-a выполняет выражение указателя:

  (&a)[n]-a
= ((a + n*sizeof(int[n])) - a) / sizeof(int)
= (n * sizeof(int[n])) / sizeof(int)
= (n * n * sizeof(int)) / sizeof(int)
= n * n