No puedo entender esta manera de calcular el cuadrado de un número

135

He encontrado una función que calcula el cuadrado de un número:

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

Devuelve el valor de n 2 . La pregunta es, ¿cómo hace eso? Después de una pequeña prueba, encontré que entre (&a)[k]y (&a)[k+1]es sizeof(a)/ sizeof(int). ¿Porqué es eso?

Emanuel
fuente
66
¿Tiene enlaces a donde encontró esta información?
R Sahu
44
int p(n)? ¿Eso incluso compila?
barak manos
78
Esto es increíble, ahora nunca lo use de nuevo y use n * n en su lugar ...
26
o mejor:int q(int n) { return sizeof (char [n][n]); }
ouah
17
@ouah asumiendo que esta pregunta se refiere a codegolf.stackexchange.com/a/43262/967, la razón por la que no utilicésizeof fue para guardar caracteres. Todos los demás: este es un código intencionalmente oscuro, es un comportamiento indefinido, la respuesta de @ ouah es correcta.
ecatmur

Respuestas:

117

Obviamente, un truco ... pero una forma de cuadrar un número sin usar el *operador (este era un requisito del concurso de codificación).

(&a)[n] 

es equivalente a un puntero a la intubicación

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

y así toda la expresión es

  (&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
Mark Lakata
fuente
11
Y como usted claramente implica pero siento la necesidad de hacer explícito, es un hack de sintaxis en el mejor de los casos. La operación de multiplicación seguirá estando allí; Es solo el operador lo que se está evitando.
Tommy
Entendí lo que sucede detrás, pero mi verdadera pregunta es por qué (& a) [k] está en la misma dirección que a + k * sizeof (a) / sizeof (int)
Emanuel
33
Como viejo codificador, me sorprende el hecho de que el compilador puede tratar (&a)como un puntero a un objeto de n*sizeof(int)cuando nno se conoce en el momento de la compilación. C solía ser un lenguaje simple ...
Floris
Es un truco bastante inteligente, pero algo que no verías en el código de producción (con suerte).
John Odom el
14
Por otro lado, también es UB, porque incrementa un puntero para apuntar ni a un elemento de la matriz subyacente, ni al pasado.
Deduplicador
86

Para comprender este truco, primero debe comprender la diferencia del puntero, es decir, ¿qué sucede cuando se restan dos punteros que apuntan a elementos de la misma matriz ?

Cuando se sustrae un puntero de otro, el resultado es la distancia (medida en elementos de la matriz) entre los punteros. Entonces, si papunta a[i]y qapunta a[j], entonces p - qes igual ai - j .

C11: 6.5.6 Operadores aditivos (p9):

Cuando se restan dos punteros , ambos apuntarán a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz; El resultado es la diferencia de los subíndices de los dos elementos de la matriz . [...]
En otras palabras, si las expresiones Py Qapuntan, respectivamente, a los elementos i-th y j-th de un objeto de matriz, la expresión (P)-(Q)tiene el valori−j siempre que el valor se ajuste a un objeto de tipo ptrdiff_t.

Ahora espero que esté al tanto de la conversión del nombre de la matriz a puntero, se aconvierte en puntero al primer elemento de la matriz a. &aes la dirección de todo el bloque de memoria, es decir, es una dirección de matriz a. La siguiente figura lo ayudará a comprender ( lea esta respuesta para obtener una explicación detallada ):

ingrese la descripción de la imagen aquí

Esto le ayudará a comprender por qué ay &atiene la misma dirección y cómo (&a)[i]es la dirección de la matriz (del mismo tamaño que la de a).

Entonces, la declaración

return (&a)[n] - a; 

es equivalente a

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

y esta diferencia dará el número de elementos entre los punteros (&a)[n]y (&a)[0], que son nmatrices de cada uno de los n intelementos. Por lo tanto, los elementos de la matriz total son n*n= n2 .


NOTA:

C11: 6.5.6 Operadores aditivos (p9):

Cuando se restan dos punteros, ambos apuntarán a elementos del mismo objeto de matriz, o uno más allá del último elemento del objeto de matriz ; El resultado es la diferencia de los subíndices de los dos elementos de la matriz. El tamaño del resultado está definido por la implementación , y su tipo (un tipo entero con signo) se ptrdiff_tdefine en el <stddef.h>encabezado. Si el resultado no es representable en un objeto de ese tipo, el comportamiento es indefinido.

Dado que (&a)[n]ni apunta a elementos del mismo objeto de matriz ni a uno más allá del último elemento del objeto de matriz, (&a)[n] - ainvocará un comportamiento indefinido .

También tenga en cuenta que es mejor cambiar el tipo de función de retorno pa ptrdiff_t.

hacks
fuente
"ambos apuntarán a elementos del mismo objeto de matriz", lo que me plantea la pregunta de si este "pirateo" no es UB después de todo. La expresión aritmética del puntero se refiere al extremo hipotético de un objeto no existente: ¿está esto permitido?
Martin Ba
Para resumir, a es la dirección de una matriz de n elementos, entonces & a [0] es la dirección del primer elemento de esta matriz, que es igual a a; Además, & a [k] siempre se considerará una dirección de una matriz de n elementos, independientemente de k, y dado que & a [1..n] también es un vector, la "ubicación" de sus elementos es consecutiva, lo que significa el primer elemento está en la posición x, el segundo está en la posición x + (número de elementos del vector a que es n) y así sucesivamente. Estoy en lo cierto? Además, este es un espacio dinámico, ¿significa que si asigno un nuevo vector de los mismos n elementos, su dirección es la misma que (& a) [1]?
Emanuel
1
@Emanuel; &a[k]es una dirección del kelemento de matriz a. Es (&a)[k]que siempre se considerará una dirección de una matriz de kelementos. Entonces, el primer elemento está en la posición a(o &a), el segundo está en la posición a+ (número de elementos de la matriz aque es n) * (tamaño de un elemento de la matriz) y así sucesivamente. Y tenga en cuenta que, la memoria para matrices de longitud variable se asigna en la pila, no en el montón.
piratea el
@MartinBa; ¿Esto está permitido? No. No esta permitido. Su UB. Ver la edición.
piratea el
1
@haccks agradable coincidencia entre la naturaleza de la pregunta y su apodo
Dimitar Tsonev
35

aes una matriz (variable) de n int.

&aes un puntero a una matriz (variable) de n int.

(&a)[1]es un puntero de intuno intpasado el último elemento de la matriz. Este puntero es n intelementos después &a[0].

(&a)[2]es un puntero de intun intpasado el último elemento de matriz de dos matrices. Este puntero es 2 * n intelementos después &a[0].

(&a)[n]es un puntero de intuno intpasado el último elemento de nmatriz de matrices. Este puntero es n * n intelementos después &a[0]. Solo resta &a[0]o ay tienes n.

Por supuesto, este es un comportamiento técnicamente indefinido, incluso si funciona en su máquina, ya (&a)[n]que no apunta dentro de la matriz o más allá del último elemento de la matriz (como lo requieren las reglas C de la aritmética del puntero).

ouah
fuente
Bueno, lo tengo, pero ¿por qué sucede esto en C? ¿Cuál es la lógica detrás de esto?
Emanuel
@Emanuel, no hay una respuesta más estricta que la aritmética del puntero útil para medir la distancia (generalmente en una matriz), la [n]sintaxis declara una matriz y las matrices se descomponen en punteros. Tres cosas útiles por separado con esta consecuencia.
Tommy
1
@Emanuel, si estás preguntando por qué alguien haría esto, hay pocas razones y todas las razones para no hacerlo debido a la naturaleza UB de la acción. Y vale la pena señalar que (&a)[n]es tipo int[n], y que se expresa como int*debido a las matrices que se expresan como la dirección de su primer elemento, en caso de que no esté claro en la descripción.
WhozCraig
No, no quise decir por qué alguien haría esto, quise decir por qué el estándar C se comporta así en esta situación.
Emanuel
1
@Emanuel Pointer Aritmética (y en este caso un subcapítulo de ese tema: diferenciación de puntero ). Vale la pena buscar en Google, así como leer preguntas y respuestas en este sitio. tiene muchos beneficios útiles y se define concretamente en las normas cuando se usa correctamente. Con el fin de comprender plenamente lo que usted tiene que entender cómo los tipos son artificiales en el código que enumeró.
WhozCraig
12

Si tiene dos punteros que apuntan a dos elementos de la misma matriz, entonces su diferencia producirá el número de elementos entre estos punteros. Por ejemplo, este fragmento de código generará 2.

int a[10];

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

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

Ahora consideremos la expresión

(&a)[n] - a;

En esta expresión atiene tipo int *y apunta a su primer elemento.

La expresión &atiene tipo int ( * )[n]y apunta a la primera fila de la matriz bidimensional con imagen. Su valor coincide con el valor de los atipos aunque son diferentes.

( &a )[n]

es el enésimo elemento de esta matriz bidimensional con imagen y tiene el tipo int[n]Es decir, es la enésima fila de la matriz con imagen. En expresión (&a)[n] - a, se convierte a la dirección de su primer elemento y tiene el tipo `int *.

Entonces, entre (&a)[n]y ahay n filas de n elementos. Entonces la diferencia será igual a n * n.

Vlad de Moscú
fuente
Entonces, ¿detrás de cada matriz hay una matriz del tamaño n * n?
Emanuel
@Emanuel Entre estos dos punteros hay una matriz de elementos nxn. Y la diferencia de los punteros da un valor igual a n * n, es decir, cuántos elementos hay entre los punteros.
Vlad desde Moscú
Pero, ¿por qué esta matriz del tamaño n * n está detrás? ¿Tiene algún uso en C? Quiero decir, es como C "asignó" más matrices del tamaño n, ¿sin que yo lo supiera? Si es así, ¿puedo usarlos? De lo contrario, ¿por qué se formaría esta matriz (quiero decir, debe tener un propósito para que esté allí).
Emanuel
2
@Emanuel: esta matriz es solo una explicación de cómo funciona la aritmética de punteros en este caso. Esta matriz no está asignada y no puede usarla. Como ya se dijo varias veces, 1) este fragmento de código es un truco que no tiene ningún uso práctico; 2) necesita aprender cómo funciona la aritmética del puntero para comprender este truco.
void_ptr
@Emanuel Esto explica la aritmética del puntero. La expresión (& a) [n] es un puntero al elemento n de la matriz bidimensional de la imagen debido a la aritmética del puntero.
Vlad desde Moscú
4
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

Así,

  1. Tipo de (&a)[n]es int[n]puntero
  2. Tipo de aes intpuntero

Ahora la expresión (&a)[n]-arealiza una sustracción de puntero:

  (&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
solo
fuente