Non riesco a capire in questo modo per calcolare il quadrato di un numero


135

Ho trovato una funzione che calcola il quadrato di un numero:

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

Restituisce il valore di n 2 . La domanda è: come lo fa? Dopo un po 'di test, ho scoperto che tra (&a)[k]e (&a)[k+1]è sizeof(a)/ sizeof(int). Perché?


6
Hai collegamenti a dove hai trovato queste informazioni?
R Sahu,

4
int p(n)? Anche quello si compila?
Barak Manos,

78
Questo è fantastico, ora non usarlo mai più e usa n * n invece ...

26
o meglio:int q(int n) { return sizeof (char [n][n]); }
ouah

17
@ouah supponendo che questa domanda si riferisca a codegolf.stackexchange.com/a/43262/967 il motivo per cui non ho usato è sizeofstato quello di salvare i personaggi. Tutti gli altri: questo è un codice intenzionalmente oscuro, è un comportamento indefinito, la risposta di @ ouah è corretta.
ecatmur

Risposte:


117

Ovviamente un hack ... ma un modo di quadrare un numero senza usare l' *operatore (questo era un requisito per il concorso di codifica).

(&a)[n] 

equivale a un puntatore intalla posizione

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

e quindi l'intera espressione è

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

11
E come chiaramente intendi, ma sento la necessità di rendere esplicito, nella migliore delle ipotesi è un hack di sintassi. L'operazione di moltiplicazione sarà ancora lì sotto; è solo l'operatore che viene evitato.
Tommy,

Ho capito cosa succede dietro, ma la mia vera domanda è perché (& a) [k] è allo stesso indirizzo di + k * sizeof (a) / sizeof (int)
Emanuel,

33
Come vecchio programmatore sono sorpreso dal fatto che il compilatore può trattare (&a)come un puntatore a un oggetto di n*sizeof(int)quando nnon è noto al momento della compilazione. C era un linguaggio semplice ...
Floris,

È un trucco piuttosto intelligente, ma qualcosa che non vedresti nel codice di produzione (si spera).
John Odom,

14
A parte questo, è anche UB, perché incrementa un puntatore in modo che non punti né a un elemento dell'array sottostante, né appena passato.
Deduplicatore

86

Per capire questo hack, prima devi capire la differenza del puntatore, cioè cosa succede quando vengono sottratti due puntatori che puntano su elementi dello stesso array ?

Quando un puntatore viene sottratto da un altro, il risultato è la distanza (misurata in elementi dell'array) tra i puntatori. Quindi, se ppunta a a[i]e qpunta a a[j], allora p - qè uguale ai - j .

C11: 6.5.6 Operatori additivi (p9):

Quando vengono sottratti due puntatori , entrambi devono puntare a elementi dello stesso oggetto array o uno oltre l'ultimo elemento dell'oggetto array; il risultato è la differenza dei pedici dei due elementi dell'array . [...].
In altre parole, se le espressioni Pe Qpuntano, rispettivamente, agli elementi i-th e j-th di un oggetto array, l'espressione (P)-(Q)ha il valore ai−j condizione che il valore si adatti a un oggetto di tipo ptrdiff_t.

Ora mi aspetto che tu sia a conoscenza della conversione del nome dell'array in puntatore, che aconverte in puntatore al primo elemento dell'array a. &aè l'indirizzo dell'intero blocco di memoria, ovvero è un indirizzo di array a. La figura seguente ti aiuterà a capire ( leggi questa risposta per una spiegazione dettagliata ):

inserisci qui la descrizione dell'immagine

Questo ti aiuterà a capire perché ae perché &aha lo stesso indirizzo e come (&a)[i]è l'indirizzo del mio array (della stessa dimensione di quello di a).

Quindi, la dichiarazione

return (&a)[n] - a; 

è equivalente a

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

e questa differenza fornirà il numero di elementi tra i puntatori (&a)[n]e (&a)[0], che sono narray ciascuno di n intelementi. Pertanto, gli elementi dell'array totale sono n*n= n2 .


NOTA:

C11: 6.5.6 Operatori additivi (p9):

Quando vengono sottratti due puntatori, entrambi devono puntare a elementi dello stesso oggetto array o uno oltre l'ultimo elemento dell'oggetto array ; il risultato è la differenza dei pedici dei due elementi dell'array. La dimensione del risultato è definita dall'implementazione e il suo tipo (un tipo intero con segno) è ptrdiff_tdefinito <stddef.h>nell'intestazione. Se il risultato non è rappresentabile in un oggetto di quel tipo, il comportamento non è definito.

Poiché (&a)[n]né punta a elementi dello stesso oggetto array né a uno oltre l'ultimo elemento dell'oggetto array, (&a)[n] - ainvocherà un comportamento indefinito .

Inoltre, è meglio cambiare il tipo di funzione di ritorno pin ptrdiff_t.


"entrambi devono indicare elementi dello stesso oggetto array" - il che solleva la questione se questo "hack" non sia UB dopo tutto. L'espressione aritmetica del puntatore si riferisce alla fine ipotetica di un oggetto inesistente: è anche permesso?
Martin Ba

Per riassumere, a è l'indirizzo di un array di n elementi, quindi & a [0] è l'indirizzo del primo elemento in questo array, che è lo stesso di a; inoltre, & a [k] sarà sempre considerato un indirizzo di una matrice di n elementi, indipendentemente da k, e poiché & a [1..n] è anche un vettore, la "posizione" dei suoi elementi è consecutiva, il che significa il primo elemento è nella posizione x, il secondo è nella posizione x + (numero di elementi del vettore a che è n) e così via. Ho ragione? Inoltre, questo è uno spazio heap, quindi significa che se allocare un nuovo vettore di stessi n elementi, il suo indirizzo è lo stesso di (& a) [1]?
Emanuel,

1
@Emanuel; &a[k]è un indirizzo kdell'elemento dell'array a. È (&a)[k]che sarà sempre considerato un indirizzo di una matrice di kelementi. Quindi, il primo elemento è nella posizione a(o &a), il secondo è nella posizione a+ (numero di elementi dell'array ache è n) * (dimensione di un elemento dell'array) e così via. Si noti che la memoria per array di lunghezza variabile viene allocata in pila, non in heap.
Hawck

@MartinBa; È anche permesso? No. Non è permesso. Il suo UB. Vedi la modifica.
Hawck

1
@haccks bella coincidenza tra la natura della domanda e il tuo soprannome
Dimitar Tsonev

35

aè una matrice (variabile) di n int.

&aè un puntatore a un array (variabile) di n int.

(&a)[1]è un puntatore di intuno intoltre l'ultimo elemento dell'array. Questo puntatore è n intelementi dopo &a[0].

(&a)[2]è un puntatore di intuno intoltre l'ultimo elemento dell'array di due array. Questo puntatore è 2 * n intelementi dopo &a[0].

(&a)[n]è un puntatore di intuno intoltre l'ultimo elemento dell'array di narray. Questo puntatore è n * n intelementi dopo &a[0]. Basta sottrarre &a[0]o ae si dispone n.

Naturalmente questo è un comportamento tecnicamente indefinito anche se funziona sul tuo computer in quanto (&a)[n]non punta all'interno dell'array o supera l'ultimo elemento dell'array (come richiesto dalle regole C dell'aritmetica del puntatore).


Bene, l'ho capito, ma perché succede in C? Qual è la logica dietro questo?
Emanuel,

@Emanuel non esiste una risposta più rigorosa a quella che l'aritmetica del puntatore è utile per misurare la distanza (di solito in un array), la [n]sintassi dichiara un array e gli array si decompongono in puntatori. Tre cose separatamente utili con questa conseguenza.
Tommy,

1
@Emanuel se stai chiedendo perché qualcuno dovrebbe fare questo, ci sono poche ragioni e tutte le ragioni per non farlo a causa della natura UB dell'azione. E vale la pena notare che (&a)[n]è di tipo int[n]e che si esprime come int*dovuto a matrici che si esprimono come l'indirizzo del loro primo elemento, nel caso ciò non fosse chiaro nella descrizione.
WhozCraig,

No, non intendevo perché qualcuno lo facesse, intendevo perché lo standard C si comporta così in questa situazione.
Emanuel,

1
@Emanuel Pointer Arithmetic (e in questo caso un sottocapitolo di quell'argomento: differenziazione puntatore ). Vale la pena cercare su google e leggere domande e risposte su questo sito. ha molti benefici utili ed è concretamente definito negli standard se usato correttamente. Al fine di cogliere appieno lo si deve capire come i tipi nel codice che hai elencato sono forzata.
WhozCraig,

12

Se hai due puntatori che puntano a due elementi dello stesso array, la sua differenza produrrà il numero di elementi tra questi puntatori. Ad esempio, questo frammento di codice genererà 2.

int a[10];

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

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

Ora consideriamo l'espressione

(&a)[n] - a;

In questa espressione aha tipo int *e punta al suo primo elemento.

L'espressione &aha tipo int ( * )[n]e punta alla prima riga della matrice bidimensionale immaginata. Il suo valore corrisponde al valore di asebbene i tipi siano diversi.

( &a )[n]

è n-esimo elemento di questo array bidimensionale immaginato e ha tipo int[n]Cioè è l'ennesima riga dell'array di imaging. In espressione (&a)[n] - aviene convertito nell'indirizzo del suo primo elemento e ha tipo `int *.

Quindi tra (&a)[n]e aci sono n righe di n elementi. Quindi la differenza sarà uguale a n * n.


Quindi dietro ogni array c'è una matrice della dimensione n * n?
Emanuel,

@Emanuel Tra questi due puntatori c'è una matrice di elementi nxn. E la differenza dei puntatori dà un valore pari a n * n, ovvero quanti elementi ci sono tra i puntatori.
Vlad da Mosca,

Ma perché è questa matrice della dimensione n * n dietro? Ha qualche utilità in C? Voglio dire, è come se C "assegnasse" più array della dimensione n, senza che io lo sapessi? In tal caso, posso usarli? Altrimenti, perché dovrebbe formarsi questa matrice (voglio dire, deve avere uno scopo perché sia ​​lì).
Emanuel,

2
@Emanuel - Questa matrice è solo una spiegazione di come funziona l'aritmetica del puntatore in questo caso. Questa matrice non è allocata e non è possibile utilizzarla. Come è stato già affermato alcune volte, 1) questo frammento di codice è un hack che non ha alcun uso pratico; 2) devi imparare come funziona l'aritmetica del puntatore per capire questo hack.
void_ptr

@Emanuel Questo spiega l'aritmetica del puntatore. Expreesion (& a) [n] è puntatore all'elemento n dell'array bidimensionale immaginato a causa dell'aritmetica del puntatore.
Vlad da Mosca,

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

Così,

  1. tipo di (&a)[n] è int[n]puntatore
  2. tipo di a è intpuntatore

Ora l'espressione (&a)[n]-aesegue una sottostrazione del puntatore:

  (&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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.