Aliasing (programare)

De la Wikipedia, enciclopedia liberă.
Salt la navigare Salt la căutare

În programare , termenul aliasing indică situația în care aceeași locație de memorie este asociată cu nume simbolice diferite în cadrul unui program.

Sens

Toate simbolurile implicate (în nume aliasate în engleză) acționează ca o „punte” către aceeași zonă de memorie: în consecință, informațiile scrise folosind unul dintre ele sunt „vizibile” prin toate celelalte, deoarece datele implicate în operațiunea de scriere sunt nu localizat direct în simbol, ci într-o zonă de memorie „comună”.

Exemple

Mai jos este o listă (neexhaustivă) de mecanisme care permit aliasarea în limbajele de programare care le susțin.

Indicatori

Aceeași locație de memorie poate fi accesată prin două simboluri diferite: variabila i și indicatorul p .

O modalitate ușoară de a crea un alias în C ++ este utilizarea unui pointer . De exemplu,

 int i = 0 ;
int * pi = & i ;

Zona de memorie ocupată de variabila i este accesibilă prin două aliasuri : variabila i și variabila pointer p . Acestea sunt două variabile distincte și independente, dar valoarea conținută într - una dintre ele - indicatorul - este un număr special, în special adresa de memorie a, care prin operatorul dereferencing vă permite să citiți și să scrie în zona de memorie indicat de către adresa & i . Ca rezultat, acest cod scrie valoarea 5 în consolă:

 * pi = 5 ;
cout << i ;

pe măsură ce instrucțiunea cout << i citește, prin variabila i , valoarea 5 inserată în interiorul ei prin indicatorul pi .

Regula strictă de aliasing

Standardul ISO pentru limbajul de programare C (inclusiv noua versiune C99, vezi secțiunea 6.5, paragraful 7) interzice, cu unele excepții, indicii de tip independent care fac referire la aceeași locație de memorie. Această regulă este cunoscută sub numele de aliasing strict .

Matrice

Unele limbaje de programare, cum ar fi C și C ++ , nu impun niciun control asupra indexurilor utilizate pentru a accesa celulele matricelor (așa-numita verificare a limitelor matricei ). Cu alte cuvinte, este posibil ca un program să acceseze celule din matrice care nu îi aparțin. În condiții speciale, acest mecanism permite accesarea unei variabile prin matrice.

Variabila i a fost alocată în celula de memorie imediat după ultima celulă care a fost rezervată matricei a .

De exemplu, dacă a este o matrice de numere întregi de celule x , stocate într-o anumită locație de memorie și o variabilă întreagă i este stocată în locațiile imediat următoare, atunci este posibil să accesați i folosind notația a [x] . [1] Astfel, în exemplu, pot fi accesate cu scrierea la [4].

Acest lucru este posibil deoarece matricele sunt de fapt realizate ca blocuri de locații de memorie adiacente, astfel încât programul stochează intern un pointer p la prima dintre aceste locații de memorie, iar accesarea matricei cu index k înseamnă pur și simplu accesarea memoriei prezente în poziția p + (k * s) unde s este dimensiunea unei celule din matrice (adică numărul de celule de memorie ocupate de o singură celulă din matrice).

Este evident că acest mecanism este posibil prin implementarea particulară a compilatorului utilizat: de exemplu, diferite implementări ar putea pune un anumit spațiu între tablouri și variabilele alocate pe teancul invocațiilor funcției , astfel încât să se alinieze variabilele la pozițiile de memorie care sunt multiple ale cuvântului specific arhitecturii sistemului utilizat. Acest lucru este posibil deoarece C nu impune o regulă generală care dictează modul în care ar trebui aranjate datele alocate în memorie. [2]

Sindicatele

Uniunile se suprapun diferite variabile în aceeași locație de memorie. Utilizarea uniunilor pentru a accesa aceeași locație de memorie prin diferite variabile nu este totuși recomandată .

Programare orientată pe obiecte

În limbajele de programare orientate obiect, fenomenul de aliasare apare atunci când mai multe variabile conțin copii ale aceleiași referințe la un anumit obiect. Vorbim despre referințe și nu neapărat indicatori, deoarece acest mecanism se găsește și în acele limbaje care nu folosesc indicatori, cum ar fi Java .

C ++

În C ++ obiectele sunt „conținute” direct în variabile, în sensul că datele care alcătuiesc obiectul sunt stocate în fiecare variabilă și nu un indicator către aceasta. Copierea unei variabile de tip obiect în alta suprascrie datele conținute în prima cu datele conținute în a doua, însă păstrează zonele de memorie distincte; în consecință, operațiile de scriere ulterioare efectuate pe o variabilă obiect afectează numai acea copie specifică și nu afectează nici originalul, nici celelalte copii.

Pentru a realiza aliasing , un indicator către zona de memorie ocupată de obiect trebuie definit în mod explicit . Poate fi asociat cu o variabilă (caz în care indicatorul trebuie să se refere la variabilă) sau să fi fost alocat dinamic, adică prin noul operator. În ambele cazuri, indicatorul va permite accesul la obiect prin intermediul operatorului -> în loc de operator .

Alternativ, clasa în cauză poate fi proiectată în mod special, astfel încât diferitele copii ale aceluiași obiect să utilizeze intern indicatori către o zonă de memorie comună (alocată prin nou ): totuși, această zonă de memorie va fi supusă aliasării și nici măcar obiectelor în sine, care rămân complet distincte unele de altele.

Java

În Java se aplică următoarea regulă: nu accesați niciodată un obiect direct, ci întotdeauna și numai printr-o referință care îl indică . Conceptual, referințele pot fi considerate indicii care pot fi folosiți cu anumite restricții: de exemplu, nu acceptă operații aritmetice, nu pot fi folosite pentru a aloca în mod explicit memoria și numai obiectele pot fi referite (nu există „referințe la variabile”) ).

O variabilă nu conține direct datele obiectului, ci o referință care indică obiectul în sine. În consecință, copierea unei variabile înseamnă de fapt copierea referinței și, desigur, aceasta înseamnă crearea a două aliasuri care indică obiectul. De fapt, operatorul xy are însăși sensul de a accesa membrul y al obiectului indicat de referința conținută în variabila x.

În următorul cod, în primul rând creați o listă, modificați-o folosind o variabilă șir (adăugând un element) și, folosind cealaltă variabilă listDiStringhe , „observați” modificarea:

 ArrayList < String > strings = new ArrayList < String > ();
ArrayList < String > listOfStrings = șiruri ;

// Adaug un șir la listă folosind referința conținută în variabila șir:
corzi . add ( "abcde" );
// Apoi, următoarea linie va scrie „abcde” pe ecran:
Sistem . afară . println ( strings . get ( 0 ));

// strings și stringList se referă la același obiect, în consecință
// următoarea linie va scrie și „abcde” pe ecran:
Sistem . afară . println ( listOfStrings . get ( 0 ));

Beneficii

În unele cazuri aliasing-ul este utilizat intenționat. De exemplu, este utilizat în mod obișnuit în Fortran ; în alte limbi poate fi folosit intenționat pentru a obține anumite beneficii.

Perl definește pentru unele construcții, cum ar fi pentru fiecare , un comportament care exploatează caracteristicile aliasării , care vă permite să modificați cu ușurință anumite structuri de date cu un cod mai sintetic și intuitiv. De exemplu:

 @array meu = ( 1 , 2 , 3 );
 
pentru fiecare element $ meu ( @array ) {
   Elementul $ $ acționează ca un alias pentru fiecare dintre
   # elemente @array, câte unul pentru fiecare buclă,
   # apoi crește $ element în bucla i-a
   # înseamnă să modificați elementul i
   # din matrice.
   $ element ++ ;
}
 
 tipăriți „@array \ n” ;

Acest cod imprimă linia ca rezultat
2 3 4
Dacă nu doriți ca interiorul buclei să schimbe matricea, puteți copia conținutul indexului într-o altă variabilă și puteți efectua operațiile pe această copie.

Dezavantaje

În anumite cazuri, aliasarea variabilelor poate duce la probleme în faza de execuție a programului. Iată câteva exemple comune.

Efecte secundare

Aliasarea parametrilor trecuți unui subrutin permite subrutinului să-și modifice valoarea, generând astfel un efect secundar (de obicei nu se dorește).

Aliasing și optimizare

Aliasarea împiedică adesea sau complică sarcina de optimizare a codului executabil încredințat unor compilatoare sau programe speciale.

Înclinarea valorilor

De exemplu, să presupunem că aveți o variabilă x în programul căruia i se atribuie valoarea 5 . Compilatorul ar putea optimiza instrucțiunile ulterioare prin substituirea constantei 5 pentru instrucțiunea care necesită citirea valorii lui x . Cu toate acestea, dacă limbajul de programare permite utilizarea pointerelor , acest lucru nu mai este posibil: de fapt, programul ar putea scrie accesul la memoria ocupată de x printr-un pointer y , de exemplu printr-o instrucțiune * y = 10 , iar acest lucru ar produce cod executabil incorect, deoarece cei care apar în codul sursă ca citiri ale variabilei x nu ar fi afectați de modificarea conținutului variabilei de la 5 la 10 .

Din acest motiv, compilatorul trebuie să facă verificări suplimentare și să adune informații despre indicii care sunt definiți în program și să întrebe: x poate fi un alias al lui * y ? Dacă răspunsul este nu , atunci optimizarea se poate face fără probleme.

Reordonarea instrucțiunilor

O altă tehnică de optimizare a programelor este schimbarea ordinii instrucțiunilor individuale care sunt executate într-un subrutină (asigurându-vă că modificările nu sunt vizibile din exteriorul aceluiași: codul optimizat produce aceleași rezultate, dar cu instrucțiuni de secvență diferite) . Dacă compilatorul determină că x nu este un alias de * y , atunci codul care citește sau scrie în x poate fi mutat înainte sau după atribuirea * y = 10 , în cazul în care acest lucru favorizează programarea instrucțiunilor sau executarea unor optimizări ulterioare.

XOR swap

Binecunoscutul algoritm numit în engleză XOR swap schimbă două variabile numerice între ele fără ajutorul unei a treia variabile temporare. Algoritmul este aplicat pe două intrări numerice x și y , efectuând acești pași în ordine:

  1. calculați valoarea x XOR y și puneți-o în variabila x
  2. calculați valoarea x XOR y și introduceți-o în variabila y
  3. calculați valoarea x XOR y și puneți-o în variabila x

Acest algoritm funcționează numai dacă valorile x și y sunt stocate în locații de memorie separate, adică dacă nu există aliasing : în caz contrar, prima dintre cele trei instrucțiuni introduce 0 în memoria comună, cu rezultatul că x și y sunt ambele. zero la sfârșitul celui de-al treilea pas. Acest lucru face necesară aplicarea unui control asupra intrărilor, ca în următoarea implementare în limbajul C :

 void xorSwap ( int * x , int * y ) {
    if ( * x ! = * y ) {
        * x ^ = * y ;
        * y ^ = * x ;
        * x ^ = * y ;
    }
}

În codul prezentat, valorile celor două intrări sunt comparate, în locul indicatoarelor respective, deoarece schimbul de două valori egale este evident superfluu. O versiune alternativă, care folosește parametrii de trecere prin referință în loc de pointer:

 void xorSwap ( int & x , int & y ) {
    dacă ( x ! = y ) {
        x ^ = y ;
        y ^ = x ;
        x ^ = y ;
    }
}

Notă

  1. ^ Primul element al matricelor din C și C ++ are index 0 , în consecință ultimul element este la indexul x - 1 , iar x indică indexul primelor poziții de memorie după cele ocupate de matrice.
  2. ^ ISO / IEC 9899: 1999, secțiunea 6.2.6.1
Informatică Portal IT : accesați intrările Wikipedia care se ocupă cu IT