• Nie Znaleziono Wyników

Łączenie z innymi językami

W dokumencie Stworzone na Wikibooks, (Stron 177-181)

Programista, pisząc jakiś program ma problem z wyborem najbardziej odpowiedniego języka do utwo-rzenia tego programu. Niekiedy zdarza się, że najlepiej byłoby pisać program, korzystając z różnych języków. Język C może być z łatwością łączony z innymi językami programowania, które podlegają kompilacji bezpośrednio do kodu maszynowego (Asembler,Fortranczy teżC++). Ponadto dzięki spe-cjalnym bibliotekom można go łączyć z językami bardzo wysokiego poziomu (takimi jak np. Python czy teżRuby). Ten rozdział ma za zadanie wytłumaczyć Ci, w jaki sposób można mieszać różne języki programowania w jednym programie.

25.1 Język C i Asembler

Informacje zawarte w tym rozdziale odnoszą się do komputerów z procesorem i i pokrewnych.

Łączenie języka C i języka asemblera jest dość powszechnym zjawiskiem. Dzięki możliwości połączenia obu tych języków programowania można było utworzyć bibliotekę dla języka C, która niskopoziomowo komunikuje się z jądrem systemu operacyjnego komputera. Ponieważ zarówno asembler jak i C są językami tłumaczonymi do poziomu kodu maszynowego, za ich łączenie odpowiada program zwany linkerem (popularny ld). Ponadto niektórzy producenci kompilatorów umożliwiają stosowanie tzw.

wstawek asemblerowy, które umieszcza się bezpośrednio w kodzie programu, napisanego w języku C. Kompilator, kompilując taki kod wstawi w miejsce tychże wstawek odpowiedni kod maszynowy, który jest efektem przetłumaczenia kodu asemblera, zawartego w takiej wstawce. Opiszę tu oba sposoby łączenia obydwu języków.

25.1.1 Łączenie na poziomie kodu maszynowego

W naszym przykładzie założymy, że w pliku f.S zawarty będzie kod, napisany w asemblerze, a f.c to kod z programem w języku C. Program w języku C będzie wykorzystywał jedną funkcję, napisaną w języku asemblera, która wyświetli prosty napis “Hello world”. Z powodu ograniczeń technicznych zakładamy, że program uruchomiony zostanie w środowiskuPOSIXna platformie i i skompilowany kompilatorem gcc. Używaną składnią asemblera będzie AT&T (domyślna dla asemblera ) Oto plik f.S:

.text .globl _f1 _f1:

pushl %ebp movl %esp, %ebp

177

movl $4, %eax /* 4 to funkcja systemowa "write" */

movl $1, %ebx /* 1 to stdout */

movl $tekst, %ecx /* adres naszego napisu */

movl $len, %edx /* długość napisu w bajtach */

int $0x80 /* wywołanie przerwania systemowego */

popl %ebp ret

.data tekst:

.string "Hello world\n"

len = . - tekst

W systemach z rodziny UNIX należy pominąć znak ” ”przed nazwą funkcji f

Teraz kolej na f.c:

extern void f1 (void); /* musimy użyć słowa extern */

int main () {

f1();

return 0;

}

Teraz możemy skompilować oba programy:

as f1.S -o f1.o gcc f2.c -c -o f2.o gcc f2.o f1.o -o program

W ten sposób uzyskujemy plik wykonywalny o nazwie “program”. Efekt działania programu powinien być następujący:

Hello world

Na razie utworzyliśmy bardzo prostą funkcję, która w zasadzie nie komunikuje się z językiem C, czyli nie zwraca żadnej wartości ani nie pobiera argumentów. Jednak, aby zacząć pisać obsługę funk-cji, która będzie pobierała argumenty i zwracała wyniki musimy poznać działanie języka C od trochę niższego poziomu.

Argumenty

Do komunikacji z funkcją język C korzysta ze stosu. Argumenty odkładane są w kolejności od ostatniego do pierwszego. Ponadto na końcu odkładany jest tzw. adres powrotu, dzięki czemu po wykonaniu funkcji program “wie”, w którym miejscu ma kontynuować działanie. Ponadto, początek funkcji w asemblerze wygląda tak:

pushl %ebp movl %esp, %ebp

Zatem na stosie znajdują się kolejno: zawartość rejestru EBP, adres powrotu a następnie argumenty od pierwszego do n-tego.

25.1. JĘZYK C I ASEMBLER 179

Zwracanie wartości

Na architekturze i do zwracania wyników pracy programu używa się rejestru EAX, bądź jego “mniej-szych” odpowiedników, tj. AX i AH/AL. Zatem aby funkcja, napisana w asemblerze zwróciła “” przed rozkazem ret należy napisać:

movl $1, %eax

Nazewnictwo

Kompilatory języka C/C++ dodają podkreślnik “ ” na początku każdej nazwy. Dla przykładu funkcja:

void funkcja();

W pliku wyjściowym będzie posiadać nazwę funkcja. Dlatego, aby korzystać z poziomu języka C z funkcji zakodowanych w asemblerze, muszą one mieć przy definicji w pliku asemblera wspomniany dodatkowy podkreślnik na początku.

Łączymy wszystko w całość

Pora, abyśmy napisali jakąś funkcję, która pobierze argumenty i zwróci jakiś konkretny wynik. Oto kod f.S:

.text

.globl _funkcja _funkcja:

pushl %ebp movl %esp, %ebp

movl 8(%esp), %eax /* kopiujemy pierwszy argument do %eax */

addl 12(%esp), %eax /* do pierwszego argumentu w %eax dodajemy drugi argument */

popl %ebp

ret /* ... i zwracamy wynik dodawania... */

oraz f.c:

#include <stdio.h>

extern int funkcja (int a, int b);

int main () {

printf ("2+3=%d\n", funkcja(2,3));

return 0;

}

Po skompilowaniu i uruchomieniu programu powinniśmy otrzymać wydruk: +=

25.1.2 Wstawki asemblerowe

Oprócz możliwości wstępnie skompilowanych modułów możesz posłużyć się także tzw. wstawkami asemblerowymi. Ich użycie powoduje wstawienie w miejsce wystąpienia wstawki odpowiedniego kodu maszynowego, który powstanie po przetłumaczeniu kodu asemblerowego. Ponieważ jednak wstawki asemblerowe nie są standardowym elementem języka C, każdy kompilator ma całkowicie odmienną filozofię ich stosowania (lub nie ma ich w ogóle). Ponieważ w tym podręczniku używamy głównie kompilatora , więc w tym rozdziale zostanie omówiona filozofia stosowania wstawek asemblera według programistów .

Ze wstawek asemblerowych korzysta się tak:

int main () {

asm ("nop");

}

W tym wypadku wstawiona zostanie instrukcja “nop” (no operation), która tak naprawdę służy tylko i wyłącznie do konstruowania pętli opóźniających.

25.2 C++

Język C++ z racji swojego podobieństwa do C będzie wyjątkowo łatwy do łączenia. Pewnym utrudnie-niem może być obiektowość języka C++ oraz występowanie w nim przestrzeni nazw oraz możliwość przeciążania funkcji. Oczywiście nadal zakładamy, że główny program piszemy w C, natomiast korzy-stamy tylko z pojedynczych funkcji, napisanych w C++. Ponieważ język C nie oferuje tego wszystkiego, co daje programiście język C++, to musimy “zmusić” C++ do wyłączenia pewnych swoich możliwości, aby można było połączyć ze sobą elementy programu, napisane w dwóch różnych językach. Używa się do tego następującej konstrukcji:

extern "C" {

/* funkcje, zmienne i wszystko to, co będziemy łączyć z programem w C */

}

W zrozumieniu teorii pomoże Ci prosty przykład: plik f.c:

#include <stdio.h>

extern int f2(int a);

int main () {

printf ("%d\n", f2(2));

return 0;

}

oraz plik f.cpp:

#include <iostream>

using namespace std;

extern "C" { int f2 (int a) {

cout << "a=" << a << endl;

return a*2;

} }

Teraz oba pliki kompilujemy:

gcc f1.c -c -o f1.o g++ f2.cpp -c -o f2.o

Przy łączeniu obu tych plików musimy pamiętać, że język C++ także korzysta ze swojej biblioteki.

Zatem poprawna postać polecenia kompilacji powinna wyglądać:

gcc f1.o f2.o -o program -lstdc++

(stdc++ — biblioteka standardowa języka C++). Bardzo istotne jest tutaj to, abyśmy zawsze pamiętali o extern “C”, gdyż w przeciwnym razie funkcje napisane w C++ będą dla programu w C całkowicie niewidoczne.

Dodatek A

W dokumencie Stworzone na Wikibooks, (Stron 177-181)