Kann nicht verstehen, wie man das Quadrat einer Zahl berechnet

Lesezeit: 9 Minuten

Benutzeravatar von Emanuel
Emmanuel

Ich habe eine Funktion gefunden, die das Quadrat einer Zahl berechnet:

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

Es gibt den Wert von n zurück2. Frage ist, wie macht es das? Nach ein wenig Testen fand ich das zwischen (&a)[k] und (&a)[k+1] ist sizeof(a)/sizeof(int). Warum ist das so?

  • Haben Sie Links, wo Sie diese Informationen gefunden haben?

    – R Sahu

    7. Januar 2015 um 21:20 Uhr

  • int p(n)? Kompiliert das überhaupt?

    – barak manos

    7. Januar 2015 um 21:20 Uhr

  • Das ist großartig, jetzt benutze es nie wieder und benutze stattdessen n * n …

    Benutzer1178540

    7. Januar 2015 um 21:32 Uhr

  • oder besser: int q(int n) { return sizeof (char [n][n]); }

    – au

    7. Januar 2015 um 22:25 Uhr

  • @ouah unter der Annahme, dass sich diese Frage bezieht codegolf.stackexchange.com/a/43262/967 der Grund, warum ich es nicht benutzt habe sizeof war es, Zeichen zu sparen. Alle anderen: Dies ist absichtlich undurchsichtiger Code, es ist ein undefiniertes Verhalten, @ouahs Antwort ist richtig.

    – ekatmur

    8. Januar 2015 um 7:43 Uhr

Benutzeravatar von Mark Lakata
Markus Lakata

Offensichtlich ein Hack … aber eine Möglichkeit, eine Zahl zu quadrieren, ohne die zu verwenden * Betreiber (dies war eine Anforderung für einen Programmierwettbewerb).

(&a)[n] 

ist äquivalent zu einem Zeiger auf int am Standort

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

und damit ist der gesamte Ausdruck

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

  • Und wie Sie deutlich andeuten, aber ich das Bedürfnis habe, es explizit zu machen, ist es bestenfalls ein Syntax-Hack. Die Multiplikationsoperation wird immer noch darunter sein; es ist nur der Operator, der vermieden wird.

    – Tommi

    7. Januar 2015 um 21:32 Uhr

  • Ich habe verstanden, was dahintersteckt, aber meine eigentliche Frage ist, warum (&a)[k] ist an der gleichen Adresse wie a + k * sizeof(a) / sizeof(int)

    – Emanuel

    7. Januar 2015 um 21:35 Uhr


  • Als alter Kauz bin ich verblüfft darüber, dass der Compiler behandeln kann (&a) als Zeiger auf ein Objekt von n*sizeof(int) Wenn n ist zur Kompilierzeit nicht bekannt. C war früher a einfach Sprache…

    – Flori

    8. Januar 2015 um 4:56 Uhr

  • Das ist ein ziemlich cleverer Hack, aber etwas, das Sie (hoffentlich) nicht im Produktionscode sehen würden.

    – Johannes Odom

    8. Januar 2015 um 16:43 Uhr

  • Abgesehen davon ist es auch UB, weil es einen Zeiger erhöht, um weder auf ein Element des zugrunde liegenden Arrays noch direkt darüber hinaus zu zeigen.

    – Deduplizierer

    8. Januar 2015 um 17:13 Uhr

Benutzeravatar von hackks
hackt

Um diesen Hack zu verstehen, müssen Sie zuerst den Zeigerunterschied verstehen, dh was passiert, wenn zwei Zeiger auf Elemente von zeigen gleiche Anordnung werden abgezogen?

Wenn ein Zeiger von einem anderen subtrahiert wird, ist das Ergebnis der Abstand (gemessen in Array-Elementen) zwischen den Zeigern. Also, wenn p verweist auf a[i] und q verweist auf a[j]dann p - q ist gleich i - j.

C11: 6.5.6 Additive Operatoren (p9):

Wenn zwei Zeiger subtrahiert werdenmüssen beide auf Elemente desselben Array-Objekts zeigen oder eins nach dem letzten Element des Array-Objekts; das Ergebnis ist die Differenz der Indizes der beiden Array-Elemente. […].
Mit anderen Worten, wenn die Ausdrücke P und Q weisen jeweils auf die i-ten und j-ten Elemente eines Array-Objekts, der Ausdruck (P)-(Q) hat den Wert i−j vorausgesetzt, der Wert passt in ein Objekt des Typs ptrdiff_t.

Jetzt erwarte ich, dass Sie sich der Umwandlung von Array-Namen in Zeiger bewusst sind. a konvertiert in einen Zeiger auf das erste Element des Arrays a. &a ist die Adresse des gesamten Speicherblocks, dh es ist eine Adresse eines Arrays a. Die folgende Abbildung hilft Ihnen zu verstehen (Lesen Sie diese Antwort für eine detaillierte Erklärung):

Geben Sie hier die Bildbeschreibung ein

Dies wird Ihnen helfen, das Warum zu verstehen a und &a hat die gleiche Adresse und wie (&a)[i] ist die Adresse von ith Array (von derselben Größe wie das von a).

Also die Aussage

return (&a)[n] - a; 

ist äquivalent zu

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

und dieser Unterschied ergibt die Anzahl der Elemente zwischen den Zeigern (&a)[n] und (&a)[0]welche sind n Arrays jeweils n int Elemente. Daher sind die gesamten Array-Elemente n*n = n2.


HINWEIS:

C11: 6.5.6 Additive Operatoren (p9):

Wenn zwei Zeiger subtrahiert werden, beide müssen auf Elemente desselben Array-Objekts zeigen oder eins nach dem letzten Element des Array-Objekts; das Ergebnis ist die Differenz der Indizes der beiden Array-Elemente. Die Größe des Ergebnisses ist implementierungsdefiniertund sein Typ (ein vorzeichenbehafteter ganzzahliger Typ) ist ptrdiff_t definiert in der <stddef.h> Header. Wenn das Ergebnis in einem Objekt dieses Typs nicht darstellbar ist, ist das Verhalten undefiniert.

Seit (&a)[n] zeigt weder auf Elemente desselben Array-Objekts noch auf eines nach dem letzten Element des Array-Objekts, (&a)[n] - a wird aufrufen undefiniertes Verhalten.

Beachten Sie auch, dass es besser ist, den Rückgabetyp der Funktion zu ändern p zu ptrdiff_t.

  • “Beide sollen auf Elemente desselben Array-Objekts zeigen” – was für mich die Frage aufwirft, ob dieser “Hack” nicht doch UB ist. Der Zeigerarithmetikausdruck bezieht sich auf das hypothetische Ende eines nicht existierenden Objekts: Ist das überhaupt erlaubt?

    – Martin B

    8. Januar 2015 um 1:06 Uhr


  • Zusammenfassend ist a die Adresse eines Arrays aus n Elementen, also &a[0] ist die Adresse des ersten Elements in diesem Array, das mit a identisch ist; außerdem &a[k] wird immer als Adresse eines Arrays von n Elementen betrachtet, unabhängig von k, und da &a[1..n] auch ein Vektor ist, ist die “Position” seiner Elemente fortlaufend, was bedeutet, dass das erste Element an Position x ist, das zweite an Position x + (Anzahl der Elemente des Vektors a, die n ist) und so weiter. Habe ich recht? Außerdem ist dies ein Heap-Speicherplatz. Bedeutet dies, dass, wenn ich einen neuen Vektor mit denselben n Elementen zuweise, seine Adresse dieselbe ist wie (& a)[1]?

    – Emanuel

    8. Januar 2015 um 1:44 Uhr


  • @Emanuel; &a[k] ist eine Adresse von ktes Element des Arrays a. es ist (&a)[k] das wird immer als Adresse eines Arrays von betrachtet k Elemente. Das erste Element ist also an Position a (oder &a), zweite ist an Position a + (Anzahl der Elemente des Arrays a welches ist n)*(Größe eines Array-Elements) und so weiter. Beachten Sie, dass Speicher für Arrays mit variabler Länge auf dem Stack und nicht auf dem Heap zugewiesen wird.

    – Hacken

    8. Januar 2015 um 2:08 Uhr


  • @MartinBa; Ist das überhaupt erlaubt? Nein. Es ist nicht erlaubt. Es ist UB. Siehe die Bearbeitung.

    – Hacken

    8. Januar 2015 um 12:18 Uhr

  • @hackks schöner Zufall zwischen der Art der Frage und deinem Spitznamen

    – Dimitar Tsonev

    13. Januar 2015 um 21:52 Uhr

Benutzeravatar von ouah
ouah

a ist ein (variables) Array von n int.

&a ist ein Zeiger auf ein (variables) Array von n int.

(&a)[1] ist ein Zeiger auf int eines int hinter dem letzten Array-Element. Dieser Zeiger ist n int Elemente nach &a[0].

(&a)[2] ist ein Zeiger auf int eines int hinter dem letzten Array-Element von zwei Arrays. Dieser Zeiger ist 2 * n int Elemente nach &a[0].

(&a)[n] ist ein Zeiger auf int eines int hinter dem letzten Array-Element von n Arrays. Dieser Zeiger ist n * n int Elemente nach &a[0]. Einfach subtrahieren &a[0] oder a und du hast n.

Natürlich ist dies ein technisch undefiniertes Verhalten, auch wenn es auf Ihrem Rechner so funktioniert (&a)[n] nicht innerhalb des Arrays oder nach dem letzten Array-Element zeigt (wie von den C-Regeln der Zeigerarithmetik gefordert).

  • Nun, das habe ich verstanden, aber warum passiert das in C? Was ist die Logik dahinter?

    – Emanuel

    7. Januar 2015 um 21:31 Uhr


  • @Emanuel darauf gibt es wirklich keine strengere Antwort als dass Zeigerarithmetik zum Messen von Entfernungen (normalerweise in einem Array) nützlich ist [n] Syntax deklariert ein Array und Arrays werden in Zeiger zerlegt. Drei separat nützliche Dinge mit dieser Konsequenz.

    – Tommi

    7. Januar 2015 um 21:34 Uhr

  • @Emanuel falls du fragst warum jemand dies tun würde, gibt es wenig Grund und jeden Grund nicht aufgrund der UB-Natur der Aktion. Und das ist erwähnenswert (&a)[n] Typ ist int[n]und das äußert sich als int* aufgrund von Arrays, die als Adresse ihres ersten Elements ausgedrückt werden, falls dies in der Beschreibung nicht klar war.

    – WhozCraig

    7. Januar 2015 um 21:35 Uhr

  • Nein, ich meinte nicht, warum jemand das tun würde, ich meinte, warum sich der C-Standard in dieser Situation so verhält.

    – Emanuel

    7. Januar 2015 um 21:37 Uhr

  • @Emanuel Zeigerarithmetik (und in diesem Fall ein Unterkapitel dieses Themas: Zeiger differenzieren). Es lohnt sich zu googeln sowie Fragen und Antworten auf dieser Seite zu lesen. es hat viele nützliche Vorteile und ist bei richtiger Anwendung in den Normen konkret definiert. Um es Ihnen vollständig zu erfassen haben zu verstehen, wie die Typen in dem von Ihnen aufgelisteten Code sind erfunden.

    – WhozCraig

    7. Januar 2015 um 21:39 Uhr


Vlad aus Moskaus Benutzer-Avatar
Vlad aus Moskau

Wenn Sie zwei Zeiger haben, die auf zwei Elemente desselben Arrays zeigen, ergibt die Differenz die Anzahl der Elemente zwischen diesen Zeigern. Dieses Code-Snippet gibt beispielsweise 2 aus.

int a[10];

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

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

Betrachten wir nun den Ausdruck

(&a)[n] - a;

In diesem Ausdruck a Typ hat int * und zeigt auf sein erstes Element.

Ausdruck &a Typ hat int ( * )[n] und zeigt auf die erste Zeile des abgebildeten zweidimensionalen Arrays. Sein Wert entspricht dem Wert von a obwohl Typen unterschiedlich sind.

( &a )[n]

ist das n-te Element dieses abgebildeten zweidimensionalen Arrays und hat einen Typ int[n] Das heißt, es ist die n-te Zeile des abgebildeten Arrays. Im Ausdruck (&a)[n] - a es wird in die Adresse seines ersten Elements konvertiert und hat den Typ `int *.

Also dazwischen (&a)[n] und a Es gibt n Zeilen mit n Elementen. Der Unterschied wird also gleich sein n * n.

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

Daher,

  1. Art der (&a)[n] ist int[n] Zeiger
  2. Art der a ist int Zeiger

Jetzt der Ausdruck (&a)[n]-a führt eine Zeigersubtraktion durch:

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

1423570cookie-checkKann nicht verstehen, wie man das Quadrat einer Zahl berechnet

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy