Powrót do strony wyboru lekcji

Łączenie asemblera z językami wysokiego poziomu



Niektóre programy napisane w językach wysokiego poziomu wymagają maksymalnej możliwej prędkości wykonywania. Instrukcje języków wysokiego poziomu wykonują się znacznie wolniej niż instrukcje asmeblera, dlatego zamiana niektórych fragmentów kodu w języku wysokopoziomowym na kod języka asmeblera może znacznie przyspieszyć działanie danego programu. Stosowanie kodu asemblerowego połączonego z instrukcjami wysokopoziomowego języka programowania w dużych projektach może być najbardziej optymalnym rozwiązaniem danego problemu programistycznego, gdyż pisanie takich projektów jedynie w kodzie asmblera jest zadaniem bardzo skomplikowanym i czasochłonnym, gdzie programista narażony jest na popełnienie szeregu niezamierzonych błędów.

Przykład

Zamieniamy na kod asemblera część kodu programu napisanego w języku C/C++. Programem wykorzystywanym do kompilacji kodu C/C++ w kursie jest program DEV C++. W zakładce narzędzia E-kursu można pobrać jego plik instalacyjny ze wskazanego adresu internetowego. Program ma za zadanie wyszukać maksymalną wartość w ciągu podanym przez użytkownika. Jego pełny kod można pobrać tutaj. Na kod asemblera zamieniamy fragment procedury znajdz , umieszczony między komentarzami:


void znajdz(int *wskaznik, int n)
{

 int pom=0;
 int i;

//-----zamieniamy na kod asemblera-----

pom=*(wskaznik+1);

for(i=1; i<=n; i++)
 {

   if(pom<*(wskaznik+i))
    {
     pom=*(wskaznik+i);
    }
 }
//------zamieniamy na kod asemblera-----

cout<< "Najwieksza wartosc w tym ciagu to:" <<pom<<endl;
cout<<",jej adres to:"<<&pom<<endl;

}

Aby "wpleść" kod asemblera w język C/C++ korzystamy z funkcji asm() . Pojedyncze instrukcje umieszczamy w cudzysłowie i zakańczamy znakiem końca linii: "\n" . W pierwszej linii funkcji asm() określamy typ składni dla instrukcji procesora. Podany fragment języka C/C++, zamienimy korzystając ze składni typu Intel. Aby uzyskać dostęp do odpowiednich parametrów i zmiennych lokalnych procedury korzystamy z rejestru EBP. Dodatnie przesunięcie względem adresu przechowywanego w rejestrze EBP pozwala nam na dostęp do adresów parametrów naszej procedury. Natomiast ujemne przesunięcie, to dostęp do adresów zmiennych lokalnych procedury. W pierwszym kroku zajmujemy się zamianą instrukcji pom=*(wskaznik+1). Jest to pierwszy parametr funkcji. Adres pierwszego parametru funkcji znajduje się pod adresem [EBP+8]. Kolejne adresy parametrów funkcji znajdują się pod adresami zgodnie z daną regułą: [EBP+4+N*4], gdzie N to numer parametru w funkcji. Pobieramy wartość wskaznik+1, tak więc początek naszego kodu będzie wyglądał następująco:


asm(".intel_syntax \n"    //rodzaj składni
     "MOV ESI,[EBP+8]\n"    //wskaznik
     "ADD ESI,4 \n"    //wskaznik+1
     "MOV EAX,[ESI] \n"    //*(wskaznik+1)

Następnie wartość wskaznik+1 przypisujemy zmiennej pom, zmienna pom, będzie miała adres [EBP-4], gdyż jest to wartość 4-ro bajtowa - int, zadeklarowana jako pierwsza zmienna lokalna w funkcji:


     "MOV [EBP-4],EAX\n"    //pom=*(wskaznik+1)

Ustawiamy zmienną inkrementującą pętli for - i , ponieważ jest to zmienna lokalna, zadekalorowana jako druga po zmiennej pom i również typu int, jej adres będzie wynosił [EBP-8]:


      "MOV EAX,4 \n"    //i=1 (ładujemy 4 - bo przesunięcie będzie o 4 bajty)
      "MOV [EBP-8],EAX \n"   

Rozpoczynamy pętle for etykietą Petla:


     "PETLA:\n"    //początek pętli for
     "MOV EBX,[EBP+12]\n"    //ładujemy zmienną n do rejestru EBX

Zmienną n pobraną jako 2 parametr funkcji i skopiowaną do rejestru EBX, mnożymy razy 4, ponieważ jest to zmienna typu int. W pętli for, będziemy inkrementować również zmienną typu int - i, która będzie zwiększana o 4 bajty:


     "MOV EAX,EBX \n"    //początek pętli for
     "MOV ECX,4\n"   
     "MUL ECX \n"    //mnożymy razy 4 wartość n

Po pomnożeniu zmiennej n przez wartość rejestru ECX, kopiujemy ją do rejestru EBX i porównujemy ze zmienną i. Jeżeli wartość zmiennej i jest większa od wartości zmiennej n, kończymy wykonanie pętli for:


     "MOV EBX,EAX \n"   
     "MOV ECX,[EBP-8]\n"    //i
     "CMP ECX,EBX \n"   
     "JA KONCZ \n"    //koniec pętli for

Następnie zamieniamy na kod asemblera fragment kodu, w którym znajdujemy maksimum ciągu:


   if(pom<*(wskaznik+i))
    {
     pom=*(wskaznik+i);
    }

Do rejestrów kopiujemy odpowiednie wartości zmiennych lokalnych i parametrów:


     "MOV EBX,[EBP-4]\n"    //pom
     "MOV EDX,[EBP+8]\n"    //wskaznik
     "MOV ECX,[EBP-8]\n"    //i

Porównujemy aktualną wartość zmiennej pom z wartością *(wskaznik+i) i sprawdzamy czy pom nie jest mniejsze od wartości odczytanej ze zmiennej *(wskaznik+i), jeśli tak wartość *(wskaznik+i) staje się nową wartością maksymalną znalezioną w naszym ciągu:


     "ADD EDX,ECX \n"    //wskaznik+i
     "MOV EDX,[EDX] \n"    //*(wskaznik+i)
     "CMP EBX,EDX \n"    //pom >=*(wskaznik+i) ?
     "JAE OMIN \n"   

     "MOV [EBP-4],EDX\n"    // pom=*(wskaznik+i);

     "OMIN: \n"   

Na końcu naszej wstawki, inkrementujemy wartość zmiennej i o kolejne 4 bajty i wykonujemy skok na początek pętli:


     "MOV ECX,[EBP-8]\n"    //i
     "ADD ECX,4 \n"    //zwiekszam o 4 wartość i
     "MOV [EBP-8],ECX\n"   

     "JMP PETLA\n"    // pom=*(wskaznik+i);

     "KONCZ:\n"   
      ".att_syntax \n");   

Cały kod wstawki przedstawiony jest na poniższym listingu, w załączniku pod listingiem umieszczony jest pełny kod programu:


asm(".intel_syntax \n"    //rodzaj składni
     "MOV ESI,[EBP+8]\n"    //wskaznik
     "ADD ESI,4 \n"    //wskaznik+1
     "MOV EAX,[ESI] \n"    //*(wskaznik+1)
     "MOV [EBP-4],EAX\n"    //pom=*(wskaznik+1)

      "MOV EAX,4 \n"    //i=1 (ładujemy 4 - bo przesunięcie będzie o 4 bajty)
      "MOV [EBP-8],EAX \n"   

     "PETLA:\n"    //początek pętli for
     "MOV EBX,[EBP+12]\n"    //ładujemy zmienną n do rejestru EBX

     "MOV EAX,EBX \n"    //początek pętli for
     "MOV ECX,4\n"   
     "MUL ECX \n"    //mnożymy razy 4 wartość n

     "MOV EBX,EAX \n"   
     "MOV ECX,[EBP-8]\n"    //i
     "CMP ECX,EBX \n"   
     "JA KONCZ \n"    //koniec pętli for

     "MOV EBX,[EBP-4]\n"    //pom
     "MOV EDX,[EBP+8]\n"    //wskaznik
     "MOV ECX,[EBP-8]\n"    //i

     "ADD EDX,ECX \n"    //wskaznik+i
     "MOV EDX,[EDX] \n"    //*(wskaznik+i)
     "CMP EBX,EDX \n"    //pom >=*(wskaznik+i) ?
     "JAE OMIN \n"   

     "MOV [EBP-4],EDX\n"    // pom=*(wskaznik+i);

     "OMIN: \n"   

     "MOV ECX,[EBP-8]\n"    //i
     "ADD ECX,4 \n"    //zwiekszam o 4 wartość i
     "MOV [EBP-8],ECX\n"   

     "JMP PETLA\n"    // pom=*(wskaznik+i);

     "KONCZ:\n"   
      ".att_syntax \n");   

Pobierz kod

Uruchomiony program przedstawiony jest na Rys.1:

Nie można wyświetlić obrazu
Rys.1. Uruchomiony program wyszukujący maksymalną wartość w ciągu .


Ćwiczenie 11

Przekształcić na kod asemblera, fragment kodu napisanego w języku C/C++. Program ma za zadanie posortować liczby metodą bąbelkową. Kod programu, który należy przekształcić to fragment procedury sortowanie:


//-----zamieniamy na kod w asemblerze-----
for (i=0; i<99; ++i)
    {
       zamiana=0;
       for (j=0; j<99-i; j++)
          if (t[j+1] < t[j])
          {
             pom = t[j];
             t[j] = t[j+1];
             t[j+1] = pom;
             zamiana=1;
          }
        if(!zamiana) break;
    }

//------zamieniamy na kod w asemblerze-----

Kod programu w języku C/C++, można pobrać z załącznika poniżej:

Pobierz kod

Przykładowe wywołanie programu przedstawione jest na Rys.2:

Nie można wyświetlić obrazu
Rys.2. Przykładowe wywołanie programu sortującego liczby metodą bąbelkową.

<Poprzednia lekcjaKolejna lekcja>