Czym jest wskaźnik

edytuj

Jak wiadomo każda komórka w pamięci RAM ma swój adres (numer), w zależności od procesora jest to liczba typu uint (23bit) lub ulong (64bit). Wynika z tego że wskaźnik jest zwykłą liczbą. Wiadomo jednak że różne zmienne mają różny rozmiar, int – 4 bajty, byte – 1 a double – 8. Wskaźniki z kolei mogą przechować jedynie numer jednego bajtu - początku zmiennej. Dlatego też wskaźniki nie są deklarowane jako zwykłe zmienne i należy poinformować kompilator o typie danych, na które wskazujemy.

Deklaracja wskaźnika

edytuj

Skoro wiemy że wskaźnik musi nie tylko zawierać liczbę, ale także informować kompilator o tym jakie dane przechowujemy nie może mieć po prostu typu pointer etc., deklarujemy je więc tak:

typ_danych *nazwa_wskaznika;

Teraz zmienna nazwa_wskaznika jest wskaźnikiem i możemy jej przypisywać adresy innych zmiennych, ale muszą być one typu typ_danych.

Pobranie adresu

edytuj

Skoro mamy już jakiś pojemnik na adres to trzeba też skądś wziąć właśnie sam „numer” komórki. Służy do tego operator &. Wystarczy poprzedzić nim nazwę zmiennej by zamiast jej zawartości operować na adresie.

int i = 5;
writefln("0x%X", &i);

Na wyjściu wypisane zostanie mniej-więcej to (wynik może być inny):

0x7FFCA3397568

Możemy też chcieć wypisać adres w formie decymalnej, wiedząc że jest to zwykła zmienna wystarczy rzutować ją na odpowiedni typ liczbowy:

int i = 5;
writefln("%d", cast(ulong) &i);

Otrzymamy wtedy wynik podobny do tego:

140728413839864

Odwołanie do wskazywanego obiektu

edytuj

Do tego na co wskazuje wskaźnik możemy odwołać się operatorem *. Wystarczy poprzedzić nim nazwę wskaźnika i już możemy operować na nim jak na normalnej zmiennej. Prosty przykład:

int num = 5;         //Zmienna typu int z wartością 5
int *num_ptr = # //Wskaźnik wskazujący na „num”
writeln(*num_ptr);   //Wypisanie danych spod wskaźnika

Stdout:

5

Skoro num i *num_ptr odnoszą się do tego samego miejsca w pamięci, to po zmianie tego na co wskazuje num_ptr zmienimy też zawartość oryginalnej zmiennej. Przykład:

int num;
int *num_ptr = #
*num_ptr = 12;
writeln(num);   //Wypisze „12”

Alokacja pamięci

edytuj

Możemy dynamicznie zaalokować pamięć na 2 sposoby, używając malloc i new.

New jest bardzo proste w obsłudze i intuicyjne, jednak w D jego możliwości są ograniczone względem C++. Jeśli chodzi o wskaźniki (bo w D nie tylko tam new znajduje zastosowanie) to możemy zaalokować tylko pamięć na typy podstawowe, struktury itd., nie można z kolei alokować tablic (bo nie mają one typu int* a int[]) i klas.

Przykład:

struct Dog
{
    string name;
    int age;
}

int main(string[] args)
{
    int* num = new int(57);
    Dog* dog = new Dog("Reksio", 5);

    writeln(*num, "\n", *dog);

	return 0;
}

Stdout:

57
Dog("Reksio", 5)

Jak widać sprawa jest raczej prosta. Trzeba tylko pamiętać że w D nie ma operatora delete i pamięć usunie GC.

Malloc & Free

edytuj

Dwie funkcje z nagłówka dają bardzo dużo kontroli nad zaalokowaną pamięcią. Sami możemy (nawet musimy) zdecydować kiedy oddać pamięć systemowi. Destruktory i konstruktory nie są wywoływane, więc w praktyce malloc i free nadają się tylko do operacji na suchych ciągach danych. Taka alokacja bardzo rzadko ma praktyczne zastosowania, więc przykład powinien wyczerpać temat:

import std.math;    //pow - kwadrat liczby
import std.stdio;   //IO
import core.memory; //Malloc & free

int main(string[] args)
{
    //Dynamiczna alokacja tablicy 4096 elementów typu ulong (↓) bez maski (to 0)
    ulong *longTab = cast(ulong*) GC.malloc(4096, 0, typeid(ulong));

    //Pętla iterująca po wszystkich elementach…
    foreach(double i; 0 .. 4096)
        longTab[cast(int) i] = cast(ulong) pow(i / 100, 2); //f(i) = (i / 100) ^ 2

    //Pętla iterująca po co 16 elemencie
    for(int i = 0; i < 4096; i += 16)
    {
        //Pętla iterująca po najbliższych 16 elementach
        foreach(int j; 0 .. 16)
            writef("%5d", longTab[i + j]);  //Wypisanie ich kolejno w wieszach po 16
        writeln();  //Zejście linię niżej
    }


    GC.free(longTab);   //Zwolnienie zaalokowanej pamięci (ok. 32KB)

	return 0;
}

Wynik ma 256 linii, więc go tu nie zamieszczę.