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.