Daftar Isi
- Pengetahuan Dasar Pemrograman Assembly dan
C
- Pemrograman Bahasa C
- Pemrograman Bahasa Assembly
- Penggunaan Assembly dan C dalam satu Program
- Beberapa Sumber Informasi
- Credits
Tutorial ini sendiri akan terbagi menjadi dua bagian besar, yaitu pemrogaman bahasa C dan pemrograman bahasa assembly, namun pada bagian akhir akan kita bahas penggunaan bahasa assembly yang terintegrasi dalam sebuah source code bahasa C, yang lazim disebut inline assembly. Penulis mengikutsertakan bagian yang agak "advanced" ini karena pada source code Tutorial Membuat Patch dalam bentuk Driver pada Windows 2000/XP teknik ini dimanfaatkan. Perlu anda ketahui bahwa bahasa assembly yang akan dibahas pada tulisan ini adalah bahasa assembly untuk arsitektur hardware x86(PC) dan tidak dapat diaplikasikan pada arsitektur hardware lainnya.
Petunjuk:
Bagi yang sudah pernah menggunakan bahasa C sebelumnya, penulis menyarankan untuk langsung membaca bagian Pointer dan Array , kemudian bagian Struct dan Union dan dilanjutkan sampai bagian akhir Pemrograman C.
Pengetahuan Dasar Pemrograman C dan Assembly
Bagian ini diperuntukkan bagi pembaca yang masih belum mengetahui pemrograman sama sekali atau yang sudah tahu tetapi ingin menyegarkan kembali ingatannya. Kita akan memulai dengan sistem bilangan yang digunakan pada komputer. Komputer hanya mengenal nilai 0 dan 1, oleh karena itu digunakanlah beberapa sistem bilangan untuk mempermudah. Sistem bilangan tersebut antara lain:
- Bilangan Biner (binary), yaitu bilangan basis 2, sehingga nilai yang dikenal
hanya 0 dan 1. Contoh : 101biner bernilai sama dengan 5desimal,
sebab 101biner = 1 x 20 + 0 x 21 + 1 x 22
atau 5desimal. Operasi matematis lainnya sama dengan bilangan desimal,
hanya di sini bilangan yang digunakan adalah basis 2.
- Bilangan Oktal (octal), yaitu bilangan basis 8, sehingga nilai yang dikenal
hanya 0 s/d 7. Contoh : 072octal (prefix 0 digunakan pada bahasa
pemrograman C) bernilai sama dengan 58desimal, sebab 072octal =
2 x 80 + 7 x 81 atau 58desimal. Operasi matematis
lainnya sama dengan bilangan desimal, hanya di sini bilangan yang digunakan
adalah basis 8.
- Bilangan Heksadesimal (Hexadecimal), yaitu bilangan basis 16, sehingga
nilai yang dikenal hanya 0 s/d 9 dan huruf A s/d F melambangkan 10desimal
s/d 15 desimal. Contoh : 0x72hexadecimal (prefix 0x
digunakan pada bahasa pemrograman C) bernilai sama dengan 114desimal,
sebab 0x72hexadecimal = 2 x 160 + 7 x 161
atau 114desimal. Operasi matematis lainnya sama dengan bilangan
desimal, hanya di sini bilangan yang digunakan adalah basis 16. Ada satu hal
yang perlu anda perhatikan, yaitu konversi dari bilangan biner ke hexadecimal
adalah suatu operasi yang "agak natural", sebab anda tinggal memecah
bilangan hexadecimal tersebut menjadi elemen-elemennya kemudian setiap elemen
direpresentasikan dengan 4 bilangan biner, maka anda telah memperoleh bilangan
biner yang bernilai sama dengan bilangan hexadecimal tersebut. Contoh: 0xA2
= ... biner, solusi: pertama pecah menjadi elemennya , kita peroleh
A dan 2. A jika direpresentasikan dalam 4 angka biner adalah 1010 (10desimal)
dan 2 jika direpresentasikan dalam 4 angka biner adalah 0010 sehingga kita
peroleh 0xA2 = 1010 0010 biner. Kemudahan operasi ini akan membantu
anda saat berurusan dengan pemrograman yang mengolah informasi bilangan biner,
jadi sangat perlu untuk dipahami.
Pemrograman atau programming secara umum dilakukan untuk membuat sekumpulan instruksi yang dapat dieksekusi (dijalankan) pada komputer. Jadi, instruksi-instruksi yang dapat dijalankan (executable) tersebut merupakan hasil akhir yang kita inginkan. Kumpulan instruksi-instruksi itulah yang disebut software. Instruksi yang dihasilkan biasanya hanya dapat dieksekusi pada satu arsitektur komputer. Instruksi yang dimaksud adalah "machine code" atau "bahasa mesin", bahasa mesin ini tidak lebih dari kumpulan bit-bit 0 dan 1 yang dapat dipahami oleh sebuah komputer. Perbedaan satu arsitektur komputer (misalnya x861) dengan arsitektur lain (misalnya Sparc2) adalah bagaimana bit-bit tersebut diorganisasikan, hal inilah yang menyebabkan machine code untuk satu macam arsitektur tidak dapat dieksekusi pada arsitektur yang lain. Prosesnya kira-kira seperti ini:
Pembuatan Machine Code --> Machine Code --> Eksekusi pada Komputer
Catatan:
1 x86 adalah keluarga microprocessor yang digunakan oleh para pemakai PC, yang termasuk ke dalam keluarga ini antara lain: Intel 80286, 80386, 80486, Pentium (i586), PentiumPro (i686),Pentium 4 (i786); AMD K6, K6-2, K6-3, Athlon (K7), Duron; Via Cyrix III; Transmeta Crusoe, dan lain-lain.
2 Sparc adalah keluarga microprocessor yang digunakan pada Server-server Sun Microsystem, yang termasuk ke dalamnya antara lain: UltraSparc II, IIIi, III.
Teknik pemrograman merupakan teknik yang digunakan untuk menghasilkan kumpulan machine code tadi. Ketika komputer digital pertama kali muncul (komputer ENIAC), untuk membuat program, orang harus langsung memasukkan bit-bit machine code tadi ke dalam komputer melalui pengaturan saklar-saklar dan punch cards (kartu yang dilubangi). Perkembangan selanjutnya adalah orang tidak perlu lagi pusing dengan bit-bit program yang sangat mudah salah (sebab anda langsung bekerja dengan angka 0 dan 1 dalam jumlah yang sangat besar), muncul lah apa yang disebut assembler, yaitu program yang dapat mengubah token-token (potongan kata-kata tertentu yang dapat dipahami oleh assembler) sederhana menjadi machine code. Karena adanya assembler, orang mulai mengenal apa yang dinamakan bahasa assembly, yaitu bahasa yang menggunakan token-token yang dapat dikenali oleh assembler, jadi bahasa assembly satu level lebih maju dibanding bahasa mesin atau machine code. Sejak saat inilah orang mulai mengenal apa yang dikatakan source code , yaitu bentuk program yang belum diolah oleh sebuah bahasa pemrograman menjadi bentuk yang dapat dieksekusi pada komputer. Source code biasanya berbentuk file yang dapat di edit.
Perlu anda ketahui, bahwa saat ini pun anda dapat memprogram dalam machine code jika anda memang benar-benar menginginkannya. Caranya mudah, anda tinggal mencari program hexeditor, misalnya Hexworkshop kemudian membuat file yang berisi machine code dalam hexadesimal (bilangan basis 16). Penulis beberapa kali melakukan hal ini karena belum mampu menggunakan assembler dengan baik (output file biner yang dihasilkan oleh assembler tidak sesuai dengan yang diharapkan). Sebenarnya jika anda membaca dan mencoba trik ke-3 pada artikel Trik Modifikasi Bios, anda telah memprogram dengan menggunakan machine code untuk microprocessor keluarga x86. Jadi, cukup mudah bukan :).
Dalam tutorial ini kita akan belajar tentang assembler. Assembler pada dasarnya bekerja dengan cara "mencocokkan (matching)". Setiap baris perintah yang anda tulis dalam bahasa assembly akan di asosiasikan dengan satu machine code tertentu, sehingga pada assembler setiap baris perintah yang anda ketikkan akan menghasilkan satu machine code. Jadi jika anda menggunakan assembler, prosesnya akan kurang lebih seperti ini (tentang linker akan dijelaskan lebih lanjut):
|
|
| |
|
V |
|
Source Code Assembly |
|
| |
|
V |
|
Assembler |
|
| |
|
V |
|
library,object file--> |
Linker |
| |
|
V |
|
Machine Code |
|
| |
|
V |
|
Eksekusi pada komputer |
Perkembangan selanjutnya adalah bahasa tingkat menengah, yaitu bahasa pemrograman C. Pada bahasa pemrograman ini, machine code dihasilkan melalui tahap yang lebih panjang. Mungkin anda bertanya, kalau orang sudah bisa membuat program dengan assembler, mengapa harus ada bahasa C ? , jawabannya adalah bahasa assembly masih terlalu "machine oriented", sukar dipahami dan bahasanya lebih dekat kepada machine code daripada ke bahasa manusia, alasan lain adalah karena dengan membuat bahasa yang levelnya lebih tinggi (lebih dekat ke bahasa manusia) maka pengembangan perangkat lunak (software) akan lebih cepat, masih ada satu alasan lagi dan mungkin yang terpenting yaitu bahasa assembly hanya dapat dieksekusi oleh satu macam arsitektur komputer saja seperti yang disebutkan sebelumnya. Jika kita dapat membuat program yang dapat berjalan di berbagai macam arsitektur komputer maka itu akan mempercepat pengembangan software. Namun demikian, itu tidak berarti bahwa setiap program yang ditulis dengan bahasa C akan dapat berjalan pada semua mesin. Misalnya, program driver yang di buat pada Tutorial Membuat Patch ... yang akan kita bahas adalah program C yang tidak portable (dapat digunakan pada komputer dengan arsitektur yang berbeda), karena tidak dapat dieksekusi di luar arsitektur komputer x86 yang menggunakan system bus PCI compliant. Software yang dijamin portable biasanya adalah software yang tidak mengutak-atik sistem, atau yang menggunakan API(Application Programming Interface) standar dan tersedia di berbagai arsitektur komputer misalnya software yang menggunakan OpenGL atau yang dibuat dengan kriteria ANSI C. Berikut ini adalah langkah-langkah pembuatan program dengan bahasa C (tentang preprocessor, compiler & linker akan dijelaskan lebih lanjut):
|
|
| |
|
V |
|
Source Code C |
|
| |
|
V |
|
Preprocessor |
|
| |
|
V |
|
Compiler |
|
| |
|
V |
|
Assembler |
|
| |
|
V |
|
library,object file--> |
Linker |
| |
|
V |
|
Machine Code |
|
| |
|
V |
|
|
Inilah garis besar dari apa yang harus kita lakukan untuk menghasilkan
sebuah program dalam bahasa C. Anda tidak perlu khawatir dengan langkah-langkah
yang begitu banyak, sebab dalam sebagian besar kasus, kita hanya perlu membuat
source code kemudian tools programming yang kita gunakan akan mengerjakan langkah
-langkah selanjutnya sampai sebuah file executable dihasilkan. Bahasa pemrograman
yang lain sebagian besar melakukan pembuatan program seperti C namun dengan
beberapa langkah tambahan, tetapi ada juga bahasa pemrograman yang menggunakan
cara yang agak berbeda, misalnya Java. Untuk mengetahui bagaimana cara kerja
bahasa tingkat tinggi lainnya anda perlu membaca buku atau tutorial dalam bahasa
yang bersangkutan.
Pemrograman Bahasa C
Setelah membaca ulasan di atas, selnjutnya kita akan membahas tentang bahasa C. Bahasa ini adalah salah satu bahasa pemrograman yang andal dan banyak digunakan. Seperti yang telah disebutkan sebelumnya, bahasa C dapat berjalan pada berbagai platform, jadi dengan mempelajari bahasa ini, pengetahuan yang anda proleh dapat digunakan untuk mmeprogram di komputer dengan arsitektur selain x86.Alasan Penggunaan Bahasa C
Bahasa C saat ini masih merupakan bahasa pemrograman yang banyak digunakan dan powerful. Sebelum melangkah lebih jauh, penulis akan menjelaskan beberapa alasan penggunaan bahasa C.
- Dalam beberapa aplikasi pemrograman pada sistem operasi windows, kita tidak
dapat atau sangat sulit menggunakan bahasa pemrograman selain C, misalnya
untuk mengaplikasikan sebuah User Interface yang belum memiliki dukungan library
untuk C++ pada visual C++ atau dalam pembuatan driver. Penulis menyebutkan
sangat sulit berarti tidak menutup kemungkinan menggunakan bahasa lain, bahasa
lain tersebut adalah C++, namun dukungan ofisial dari microsoft untuk penggunaan
C++ pada kasus yang disebutkan sebelumnya belum ada sehingga anda harus membuat
semacam "wrapper" sendiri jika ingin menggunakan C++ dan hal ini
sulit dilakukan bagi pemrogram pemula. Perlu diketahui bahwa Windows saat
ini belum merupakan sistem operasi yang benar-benar object oriented (C++ adalah
bahasa pemrograman yang object oriented) dan "core" dari sistem
operasinya sendiri masih diimplementasikan dalam bahasa C, sehingga fleksibilitas
terbesar dalam membuat software untuk windows akan kita peroleh jika kita
menggunakan bahasa C. Namun demikian, cara ini juga merupakan salah satu cara
yang cukup sulit bagi para pemrogram pemula, namun anda tidak perlu khawatir,
sebab dalam tutorial ini akan dijelaskan prinsip dasarnya.
- Beberapa software membutuhkan kinerja yang tinggi dari segi kecepatan,
hal ini bisa dicapai dengan mudah jika kita menggunakan bahasa pemrograman
yang "sederhana" seperti C. Penulis sendiri telah membuat beberapa
software dalam dua versi, yaitu C "murni" dan C++ , kemudian membandingkan
kinerjanya, ternyata software yang diimplementasikan dengan C memiliki kinerja
yang lebih tinggi.
- Kelemahan dari bahasa C yang penulis ketahui sampai saat ini adalah dari
segi kompleksitas pemeliharaan dan pengembangan software yang kita buat, jika
software tersebut sudah cukup kompleks. Sebagai contoh, software ExploChip
pada artikel Tutorial Membuat Patch... dibuat dengan C++, C dan Assembly,
namun C++ merupakan bahasa yang terbanyak digunakan untuk memudahkan pemeliharaan
dan pengembangan.
Pengenalan Sintaks Bahasa C
Sintaks adalah suatu bentuk dasar (biasanya kata) yang dapat dipahami dan diolah oleh compiler. Pada bagian ini akan di bahas beberapa sintaks yang umum digunakan dalam bahasa C, selain itu akan dijelaskan bagaimana cara kerja compiler C secara umum. Sebelum melangkah lebih jauh, perlu anda ketahui bahwa C adalah bahasa pemrograman yang case sensitive, sehingga var, Var, dan VAR adalah tiga hal yang berbeda pada bahasa C.
Preprocessor dan Macro
Preprocessor3 adalah bagian dari sebuah software development tool4
untuk bahasa C yang bertugas untuk melakukan pengolahan source code sebelum
diberikan kepada compiler untuk diolah lebih lanjut. Preprocessor pada dasarnya
menerjemahkan source code yang kita buat ke bentuk yang dapat dikenali oleh
compiler. Dalam bahasa C, ada beberapa keyword5 yang sebenarnya tidak
dikenali oleh compiler, umumnya keyword ini diawali dengan #, misalnya #macro,
#define
, #include
, #pragma dan lain-lain, keyword
inilah yang diolah oleh preprocessor. Preprocessor merupakan salah satu bagian
Software Development Tool yang tergantung kepada vendor yang membuat tool tersebut,
namun demikian, kita akan membahas beberapa keyword yang umum diolah oleh preprocessor
(telah di standarisasi). Keyword yang diolah oleh preprocessor antara lain:
Catatan:
3 Preprocessor juga ada dalam bahasa lain, tetapi yang kita maksud di sini adalah preprocessor untuk bahasa C.
4 Software development Tool misalnya Turbo C, Visual C++, Borland C++ Builder, GNU C dan lain-lain yang dapat mengolah source code C.
5 Kata-kata yang mempunyai arti khusus dan digunakan secara internal oleh compiler.
#include
, keyword ini membuat kita seakan-akan
telah mengetik isi dari file yang dicantumkan sesudah keyword tersebut. Misalnya:
#include < stdio.h >
akan membuat preprocessor mengekspansikan
file stdio.h pada tempat keyword #include
tadi diketikkan. #include
mempunyai dua macam bentuk yaitu #include
<...>
dan #include
"..."
, titik-titik tersebut
adalah nama file. #include
<...>
akan membuat
preprocessor mencari file yang namanya dicantumkan di dalam kurung pada direktori-
direktori yang telah didefinisikan oleh software development tool yang kita
gunakan, misalnya pada direktory INC, INCLUDE
, dan lain-lain. #include
"..."
akan membuat preprocessor mencari file yang namanya
dicantumkan di dalam tanda petik ganda pada direktori file yang memiliki keyword
#include
tersebut. Misalnya anda mengerjakan file test.c yang ada
pada direktori bernama test, dan anda mengetikkan #include
"test.h"
,
maka preprocessor akan mencari file test.h pada direktori test.
#define,
keyword ini mempunyai format: #define
identifier token-stringopt
. Identifier adalah nama sebuah
konstanta yang akan kita gunakan dalam program kita, dan token-string
adalah nilai dari identifier tersebut (nilai ini harus dapat dikenali oleh compiler),
token-string
dapat merupakan sebuah ekspresi6. Preprocessor akan
mengganti setiap kemunculan identifier dengan nilai pada token-string
.
Jika token-string
dikosongkan, maka identifier tadi akan hilang
dari source code program kita (pada baris-baris selanjutnya, identifier menjadi
tidak dikenali sebab tidak memiliki nilai lagi). Contoh: #define
a 0xff
akan membuat setiap kemunculan a dalam program, misalnya
Function1(a)
diganti dengan 0xff
(255 desimal), dan
jika sesudah baris Function1(a) ada baris #define
a , maka a sudah
tidak akan dikenali lagi pada bagian selanjutnya. Dengan demikian penggunaan
a pada baris-baris selanjutnya adalah ilegal (compiler akan memberikan pesan
kesalahan saat program di buat/ di-compile). Jika pada #define identifier
token-stringopt
, token-string
merupakan sebuah
ekspresi, maka identifier tersebut disebut sebagai makro. Makro akan diolah
preprocessor, setiap kemunculan identifier
pada source code yang
akan diganti dengan ekspresi token-string
(jika baris source code
tsb tidak mengandung #
sebagai karakter pertamanya), ekspresi ini
kemudian diolah oleh compiler.
Catatan:
6 Ekspresi adalah sekumpulan operator (misalnya +, -, dll) dan operand (variabel yang dikenai operasi) yang mengerjakan salah satu kombinasi aktivitas berikut: Menghitung nilai, memindahkan nilai ke dalam suatu objek atau fungsi (fungsi akan dijelaskan lebih lanjut), atau menghasilkan efek samping (misalnya membuat sebuah baris program dieksekusi atau tidak).
Contoh:
makro #define a ((c)*(d))
akan mengganti setiap kemunculan a dengan
nilai hasil perkalian variabel c dan d. Tanda kurung yang banyak untuk memastikan
bahwa makro kita dieksekusi sesuai dengan yang kita inginkan, sebab ada yang
disebut dengan operator precedence, yaitu urutan pengerjaan operator jika berbagai
operator muncul dalam sebuah pernyataan. Misalnya: a*b+c
, pada
ekspresi ini, yang akan diolah terlebih dulu adalah perkalian a dan b, kemudian
hasilnya dijumlahkan dengan c.
makro dapat mempunyai parameter(nilai input). Berikut ini contohnya:
#define kali(a,b) ((a)*(b))
....
int y = 20;
int z = 90;
int x = kali(y,z);
Pada source code di atas, int adalah tipe data variabel, kita akan belajar
lebih lanjut tentang hal ini. Saat preprocessor menemukan ekspresi kali(y,z)
, maka nilai y dan z dari baris-baris sebelumnya akan menggantikan y dan z pada
kali(y,z)
, kemudian kali(y,z)
berubah menjadi ((y)*(z))
sehingga diperoleh 20x90 = 1800
, kemudian hasil ini dipindahkan
ke x (tanda = artinya pindahkan nilai di sebelah kanan tanda ini ke variabel
yang ada di sebelah kiri tanda ini).
#if
, keyword ini digunakan untuk menentukan
pengolahan baris-baris source code sesudahnya, sampai dengan keyword #endif
.
Formatnya adalah : #if
expression , expression adalah sebuah ekspresi
yang valid (dapat diolah oleh compiler). Jika ekspresi tersebut benar maka baris-baris
source code sesudah #if
akan diolah dan sebaliknya jika ekspresi
tersebut bernilai salah. Keyword ini harus digunakan bersama dengan keyword
#endif
untuk menandakan batas penggunaan keyword tersebut dalam
source code. Contoh:
#define TEST 2
#if (TEST > 0)
...
baris source code;
#endif
Pada potongan program diatas, baris source code akan diolah sebab ekspresi
TEST > 0
adalah benar.
#ifdef
, keyword ini penggunaannya sama dengan
#if
. Formatnya: #ifdef
identifier
, identifier
adalah sebuah konstanta. Jika identifier telah didefinisikan sebelumnya (dengan
keyword #define
) maka baris-baris source code sesudah #ifdef
akan diolah oleh kompiler, demikian pula sebaliknya. Keyword ini harus digunakan
bersama dengan keyword #endif
untuk menandakan batas penggunaan
keyword tersebut dalam source code
#ifndef
, keyword ini mempunyai format yang
sama dengan #ifdef
, tetapi cara kerjanya adalah kebalikan dari
#ifdef
, sehingga jika baris- baris program sesudahnya justru diolah
jika identifier tidak didefinisikan sebelumnya. Keyword ini harus digunakan
bersama dengan keyword #endif
untuk menandakan batas penggunaan
keyword tersebut dalam source code
#endif
, keyword ini sebagai pembatas untuk menadakan bagian
akhir dari source code yang akan dikenai efek jika keyword #if
,
#ifdef
dan #ifndef
dievaluasi.
#undef
, adalah keyword untuk menonaktifkan identifier
yang telah didefinisikan dengan #define
, efeknya sama dengan #define
identifier
. Dengan demikian kita dapat mendefinisikan kembali
identifier tadi setelah #undef.
#else
, keyword ini digunakan di antara keyword #if
,
#ifdef
, #ifndef
dengan keyword #endif
.
Jika ekspresi pada keyword #if
, #ifdef
, #ifndef
benar maka source code sesudah #else, tidak akan dieksekusi,demikian pula sebaliknya.Contoh:
#define TEST 0
Pada potongan program diatas,
#if (TEST > 0)
...
baris source code1;
#else
...
baris source code2;
#endifbaris source code2;
akan diolah sebab ekspresiTEST > 0
adalah salah.
Compiler
Compiler adalah bagian dari Software Development Tool yang kita gunakan, yang
bertugas menerjemahkan source code yang telah diolah oleh prepocessor menjadi
bahasa assembly yang selanjutnya akan diolah oleh assembler untuk dijadikan
machine code yang dapat dieksekusi. Bahasa C dikatakan sebagai bahasa pemrograman
yang portable, sebab dengan memberikan switch tertentu kepada compiler C pada
saat kompilasi dilakukan, compiler akan menghasilkan file assembly yang berbeda,
sesuai dengan tipe arsitektur yang kita pilih (dengan menggunakan switch tersebut),
namun kemampuan ini juga tergantung kepada Software development Tool yang kita
gunakan.
Linker
Linker adalah bagian dari Software development Tool yang bertugas mengubah
format machine code yang dihasilkan oleh assembler menjadi instruksi yang dapat
dieksekusi. Jadi sebenarnya assembler tidak menghasilkan sebuah program yang
dapat dieksekusi. Sebuah file yang dapat dieksekusi mempunyai format header
tertentu, dan dari satu sistem operasi ke sistem operasi yang lain formatnya
berbeda-beda. Linker juga mempunyai fungsi lain, yaitu menyatukan komponen-komponen
dari software yang kita buat ke dalam file executable yang nantinya akan dihasilkan
setelah melalui linker, misalnya software tersebut mempunyai komponen eksternal
seperti resource pada windows, resource sebenarnya bukan bagian dari program
yang dieksekusi tetapi ikut disertakan dalam program karena dibutuhkan , contoh
resource: ikon yang akan ditampilkan pada sebuah program. Komponen lain yang
kadang-kadang disatukan oleh linker adalah dll (dynamic link library) yang disatukan
secara "static" ke dalam file executable, selain itu linker juga menyatukan
file objek (obj) diluar file objek yang kita hasilkan jika dibutuhkan oleh program
kita ke dalam executable yang akan dibentuk.
Pernyataan
dalam bahasa C adalah sebuah baris program yang dapat diproses oleh compiler.
Pernyataan diakhiri dengan tanda ;
(titik-koma). Setiap pernyataan
dapat berisi beberapa ekspresi, operator maupun operand. Contoh: return;
Sekelompok pernyataan yang disatukan dalam sebuah kurung kurawal (kadang di
sebut sebagai block of statement atau blok
pernyataan ) juga diperlakukan seperti sebuah pernyataan biasa, hal
ini terutama berguna saat anda akan mengatur eksekusi sekelompok pernyataan
dalam sebuah percabangan seperti contoh di bawah ini:
if (a > b)
jika ekspresi didalam kurung sesudah
{
pernyataan 1;
pernyataan 2;
pernyataan 3;
} if ( ekspresi a > b)
bernilai benar maka seluruh pernyataan di dalam kurung kurawal akan dieksekusi.
Sebenarnya keyword if hanya dapat mengeksekusi satu pernyataan sesudah
ekspresi yang diuji olehnya ( pada contoh di atas ekspresi a > b
),
namun dengan adanya kurung kurawal, pernyataan 1 s/d pernyataan 3 seolah-olah
dianggap satu pernyataan saja oleh compiler yang kita gunakan.
Variabel
adalah sebuah simbol yang mewakili sebuah alamat di memory yang nilainya dapat
dimanipulasi melalui nama tersebut dan mempunyai ukuran tertentu. Ukuran ini
disebut tipe data. Tipe data yang berbeda kemungkinan mempunyai ukuran yang
berbeda. Sintaks untuk mendeklarasikan (menyatakan pertama kali adanya sebuah
variabel) adalah:
tipe-data NamaVariabel
Contoh:
int variabel1;
Berikut ini adalah tipe data dan ukurannya.
Tipe data |
Ukuran (byte) |
Contoh |
bool |
1 |
bool a = true ; bool b = false |
int, unsigned int |
4 |
int a = -255 ; unsigned int b = 10 |
char, unsigned char |
1 |
char a = -1 ; unsigned char b = 1 |
long, unsigned long |
4 |
long c = 0xFF ; unsigned long d = 0xFFC |
short, unsigned short |
2 |
short e = 0xFFF ; unsigned short f = 067 |
float |
4 |
float g = 0.001 |
double |
8 |
double h = 1.02e8 |
long double |
8 |
long double h = 2.42e10 |
Variabel bertipe bool
hanya mempunyai dua nilai yaitu true
atau false, jadi anda hanya dapat mengisinya dengan nilai ini. Perlu
diperhatikan bahwa variabel bertipe bool
dianggap bernilai false
jika nilainya 0 , dan benar jika nilainya >= 1
. Mungkin anda
bertanya, mengapa untuk dua alternatif saja kita menggunakan 1 byte (8 bit)
padahal untuk 2 alternatif hanya dibutuhkan 1 bit, hal ini disebabkan oleh arsitektur
kebanyakan microprocessor saat ini adalah "byte addessable", maksudnya
nilai variabel terkecil yang dapat ditangani oleh microprocessor tersebut adalah
byte. Variabel bertipe int
(integer/bilangan bulat) dan unsigned
int
(unsigned integer/bilangan bulat positif) hanya dapat diisi dengan
bilangan yang bernilai bulat (tidak ada nilai di belakang koma atau pecahan)
termasuk 0. Pada sistem operasi windows variabel dengan tipe ini memiliki nilai
32 bit (4 byte), sehingga untuk unsigned int nilai maksimumnya adalah 232
-1 (karena ada 0), int nilainya berada pada kisaran -(216)
sampai 216-1 (karena ada 0). Untuk variabel yang tipenya lain tentunya
anda sudah dapat menghitung sendiri nilai kisarannya, karena semuanya analog
dengan tipe data int. Anda hanya perlu mengetahui jumlah bit yang digunakan
variabel tersebut dan untuk yang signed, nilai negatif yang paling kecilnya
adalah - (2(jumlah bit tipe data tsb/2)) dan nilai maksimum positifnya
adalah 2 (jumlah bit tipe data tsb/2)-1 (karena ada 0). Namun demikian,
ada tiga tipe variabel yang tidak termasuk ke dalam golongan yang memenuhi aturan
ini, yaitu float, double
dan long double
. Ketiga tipe
ini adalah tipe variabel floating point, yaitu variable yang dinyatakan
dalam mantissa dan eksponen. Mantissa adalah faktor pengali, dan
eksponen adalah bilangan berpangkat 10 yang dikalikan dengan mantissa tersebut.
Misalnya 1,045x10e5 , pada bilangan ini mantissa adalah 1,045 sedangkan eksponennya
adalah 10e5 (105). Pada prakteknya variabel tipe floating point ditangani
oleh bagian FPU (Floating Point Unit/Math Coprocessor) pada microprocessor kita.
Pada variabel floating point anda dapat memasukan data dengan nilai pecahan
yang dikalikan dengan eksponesial, misalnya 1,5x1032.Tipe data floating
point nilai maksimum dan minimumnya tergantung pada Software Development Tool
yang kita gunakan, pada Visual C++ , kisarannya sebagai berikut: float mempunyai
nilai antara 10-308 s/d 10308, double mempunyai nilai
antara -1.79769313486231x10308 s/d -4.94065645841247x10 -324
untuk bilangan negatif, dan 4.94065645841247x10-324 s/d 1.79769313486231x10308
untuk bilangan positif, dan 0. Pada beberapa sistem long double dan double mempunyai
ukuran berbeda, tetapi pada Microsoft Visual C++, keduanya adalah identik. Pada
saat menentukan tipe data sebuah variabel yang akan anda gunakan anda harus
memperhatikan nilai maksimum atau minimum yang dapat ditampung oleh tipe data
tersebut agar tidak terjadi kesalahan perhitungan, nilai dari variabel tersebut
harus kurang dari atau sama dengan nilai maksimum yang dapat ditampung oleh
tipe data tersebut. Anda juga dapat memasukkan bilangan bukan desimal(basis
10) ke dalam variabel yang bukan floating point. Misalnya, anda dapat memasukkan
nilai heksa desimal (basis 16) seperti 0xFF
, bilangan octal (basis
8) seperti 0223
. Selain sifat ini, variabel dengan tipe data char
mempunyai sifat tersendiri, yaitu anda dapat memasukkan sekumpulan ke dalam
variabel tersebut, misalnya pada potongan source code berikut:
char message = "Ini adalah string"
variabel char dengan deklarasi seperti di atas sering di sebut string, sebab
menyimpan sekumpulan karakter, nilai variabel ini tidak begitu penting sebab
yang kita simpan bukan merupakan nilai yang akan dioperasikan.
Variabel memiliki apa yang disebut variable scope. Variable scope adalah
daerah di dalam source code tempat sebuah variabel masih dikenali/terlihat oleh
compiler. Misalnya seperti ini: anda mendeklarasikan sebuah variabel di dalam
sebuah fungsi di luar fungsi main(),
maka variabel tersebut hanya
memiliki scope pada fungsi itu saja, sebab di luar fungsi tersebut variabel
tersebut tidak akan dikenali oleh compiler. Variabel seperti ini biasa disebut
variabel lokal. Kasus kedua adalah variabel yang dideklarasikan diluar semua
fungsi, variabel seperti ini mempunyai scope dikeseluruhan source code yang
kita miliki. Variabel seperti ini biasa juga disebut variabel global sebab variabel
ini dikenali di dalam fungsi manapun dalam program kita, termasuk fungsi main()
.
Implikasi dari adanya variabel global dan lokal adalah:
- Anda tidak dapat menggunakan nama variabel yang sama untuk sebuah variabel
global dan lokal sebab compiler akan menganggap variabel tersebut dideklarasikan
2 kali dan hal ini tidak dibolehkan oleh compiler C.
- Anda tidak dapat menggunakan variabel lokal di luar fungsi tempat variabel tersebut dideklarasikan, sebab variabel tersebut tidak akan dikenali oleh compiler.
Untuk memperjelas, akan diberikan contoh sebagai berikut:
#include <stdio.h>
#define UINT unsigned int
/* deklarasi fungsi kali */
UINT kali(UINT a, UINT b);
UINT var_test;
void main()
{
UINT faktor1 = 10;
UINT faktor2 = 20;
UINT hasil ;
hasil = kali(faktor1,faktor2);
printf("%d \n",hasil);
}
UINT kali(UINT a, UINT b)
{
UINT hasil = 30;
UINT c = a*b;
return c;
}
Pada source code di atas, var_test
adalah variabel global, sebab
variabel ini dideklarasikan di luar semua fungsi. Variabel faktor1
,
faktor2
dan hasil adalah variabel lokal pada fungsi main()
.
Variabel hasil
yang ada pada fungsi kali adalah variabel lokal,
yang berbeda dengan variabel hasil
yang ada pada fungsi main()
sebab scope kedua variabel tersebut adalah berbeda. Anda perlu memperhatikan
hal seperti ini saat anda membuat program, sebab jika anda dengan seenaknya
memberi nama pada variabel yang anda gunakan tanpa memperhatikan scope dari
variabel tersebut, bisa-bisa program anda tidak dapat di-compile.
Operator
Operator adalah karakter atau kumpulan karakter yang digunakan untuk
memanipulasi variabel. Karakter atau kumpulan karakter ini dikenali secara spesifik
oleh compiler sehingga variabel yang diubah-ubah nilainya(operand) akan dikenai
operasi sesuai dengan definisi operasi yang dimiliki oleh operator tadi. Dalam
bahasa C ada tiga golongan besar operator, yaitu:
unary operator, operator ini hanya bekerja pada satu operand. Unary
operator antara lain:
Operator Unary |
Kegunaan |
- ~ ! |
Operator ini melakukan operasi negasi, yaitu merubah nilai operand menjadi nilai yang berlawanan tanda. Disebut juga Negation atau complement operator |
++ |
Operator ini menaikkan nilai operand 1 satuan, disebut juga increment operator. |
-- |
Operator ini menurunkan nilai operand 1 satuan, disebut juga decrement operator. |
* |
Operator ini memberikan nilai yang tersimpan pada alamat memory yang ditunjuk oleh operand, disebut juga indirection operator. Biasanya digunakan dalam operasi dengan pointer (akan dijelaskan lebih lanjut) |
& |
Operator ini memberikan alamat memory operand. Biasanya digunakan dalam operasi dengan pointer (akan dijelaskan lebih lanjut). Disebut juga address of operator. |
sizeof |
Operator ini memberikan ukuran memory yang digunakan operand
dalam byte. operand adalah operand yang ukurannya akan dicari. |
binary operator, operator ini bekerja pada dua operand sekaligus. Operator
binary antara lain:
Operator Binary |
Kegunaan |
Operator Matematis |
|
+ |
Operator ini menjumlahkan nilai operand di sebelah kiri dengan nilai operand di sebeleh kanan |
- |
Operator ini mengurangi nilai operand di sebelah kiri dengan nilai operand di sebeleh kanan |
* |
Operator ini mengalikan nilai operand sebelah kiri dengan nilai operand di sebelah kanan |
/ |
Operator ini membagi nilai operand sebelah kiri dengan nilai operand di sebelah kanan |
% |
Operator ini disebut juga modulo operator. Nilai Operand di
sebelah kiri akan dibagi dengan nilai operand di sebelah kanan, kemudian
hasil operasinya adalah sisa dari pembagian tersebut. Misalnya: 3%2
akan menghasilkan 1 . |
Operator Logic (Logical Operator) |
|
&& |
Operator ini disebut operator logical and. Cara kerjanya adalah mengetes ekspresi disebelah kanan dan kirinya, jika keduanya benar maka nilai yang dihasilkan 1 (true), jika tidak maka nilai yang dihasilkan adalah 0 (false) |
|| |
Operator ini disebut operator logical or. Cara kerjanya adalah mengetes ekspresi disebelah kanan dan kirinya, jika salah satunya benar maka nilai yang dihasilkan 1, jika tidak maka nilai yang dihasilkan adalah 0 |
Operator Operasi Bit (Bitwise Operator) |
|
& |
Operator disebut juga operator bitwise and. Cara kerjanya
adalah melakukan operasi AND (&) pada setiap bit kedua operand.
misalnya: 0xA & 0xC akan menghasilkan 0x8 ,
perhatikan bahwa 0xA = 1010biner dan 0xC =
1100biner . Sehingga 1010 & 1100 = 1000biner
atau 0x8heksadesimal sebab 0&1 = 0 , 1&0
= 0, 0&0 = 0, 1&1 = 1 . |
| |
Operator ini disebut juga operator bitwise or. Cara kerjanya
adalah melakukan operasi OR (|) pada setiap bit kedua operand.
misalnya: 0xA | 0xC akan menghasilkan 0xE , perhatikan
bahwa 0xA = 1010 biner dan 0xC = 1100biner .
Sehingga 1010 | 1100 = 1110biner atau 0xE
heksadesimal sebab 0|1 = 1 , 1|0 = 1, 0|0 = 0, 1|1 = 1. |
^ |
Operator ini disebut juga operator bitwise xor. Cara kerjanya
adalah melakukan operasi XOR (^) pada setiap bit kedua operand.
misalnya: 0xA ^ 0xC akan menghasilkan 0x6 , perhatikan
bahwa 0xA = 1010biner dan 0xC = 1100biner .
Sehingga 1010 | 1100 = 0110biner atau 0x6heksadesimal
sebab 0^1 = 1 , 1^0 = 1, 0^0 = 0, 1^1 = 0. |
Operator Penggeseran Bit (Bit Shift Operator) |
|
>> |
Operator ini disebut operator geser kanan (right shift). Cara
kerjanya adalah melakukan penggeseran bit ke kanan pada nilai operand sejumlah
bit yang ada di sebelah kanan operator ini. Misalnya: 0xA >>
3 akan menghasilkan 0x1 , perhatikan bahwa 0xA
= 1010biner , sehingga dengan menggeser bitnya 1 kali ke
kanan diperoleh 101biner , dan jika digeser 3 kali
akan diperoleh 1biner atau 0x1heksadesimal
|
<< |
Operator ini disebut operator geser kiri (left shift). Cara
kerjanya adalah melakukan penggeseran bit ke kiri pada nilai operand sejumlah
bit yang ada di sebelah kanan operator ini. Misalnya: 0xA <<
3 akan menghasilkan 0x50 , perhatikan bahwa 0xA
= 1010biner , sehingga dengan menggeser bitnya 1 kali ke
kiri diperoleh 10100 biner , dan jika digeser 3 kali akan diperoleh
1010000biner atau 0x50heksadesimal .
|
Operator Perbandingan (Relational Operator) |
|
== |
Operator ini bekerja dengan membandingkan nilai operand di
sebelah kanan dan sebelah kiri-nya, jika keduanya identik maka hasilnya
adalah 1 (true). Misalnya:A = 2; pada source code ini ...code1 akan di eksekusi, sebab A==B
bernilai 1 (true). |
!= |
Operator ini bekerja dengan membandingkan nilai operand di
sebelah kanan dan sebelah kiri-nya, jika keduanya berbeda maka hasilnya
adalah 1 (true). Misalnya:A = 2; pada source code ini ...code2 akan di eksekusi, sebab
A!=B bernilai 0 (false). |
<= |
Operator ini bekerja dengan membandingkan nilai operand di
sebelah kanan dan sebelah kiri-nya, jika operand di kiri bernilai kurang
dari atau sama dengan operand di sebelah kanan maka hasilnya adalah 1 (true).
Misalnya:A = 2; pada source code ini ...code1 akan di eksekusi, sebab
A<=B bernilai 1 (true). |
> |
Operator ini bekerja dengan membandingkan nilai operand di
sebelah kanan dan sebelah kiri-nya, jika operand di kiri bernilai lebih
dari operand di sebelah kanan maka hasilnya adalah 1 (true). Misalnya:A = 3; pada source code ini ...code1 akan di eksekusi, sebab A
> B bernilai 1 (true). |
>= |
Operator ini bekerja dengan membandingkan nilai operand di
sebelah kanan dan sebelah kiri-nya, jika operand di kiri bernilai lebih
dari atau sama dengan operand di sebelah kanan maka hasilnya adalah 1 (true).
Misalnya:A = 4; pada source code ini ...code1 akan di eksekusi, sebab
A>=B bernilai 1 (true). |
< |
Operator ini bekerja dengan membandingkan nilai operand di
sebelah kanan dan sebelah kiri-nya, jika operand di kiri bernilai kurang
dari operand di sebelah kanan maka hasilnya adalah 1 (true). Misalnya:A = 2; pada source code ini ...code1 akan di eksekusi, sebab A
< B bernilai 1 (true). |
ternary operator, operator ini bekerja pada tiga operand sekaligus.
Operator |
Kegunaan |
Ekspresi1 ? Ekspresi2 : Ekspresi3 |
Cara kerja dari operator ini adalah mengecek nilai ekspresi1 ,
jika nilainya benar, maka yang akan dievaluasi adalah ekspresi2 ,
jika salah maka yang akan dievaluasi adalah ekspresi3 . Berikut
ini contoh untuk memperjelas: int z; pada source code di atas, nilai z akan sama dengan y, sebab ekspresi1
nilainya salah, sehingga yang akan dievaluasi adalah ekspresi3 . |
Percabangan
Percabangan (Control Flow) adalah teknik yang digunakan untuk mengalihkan eksekusi
program saat suatu ekspresi yang diuji bernilai benar atau salah. Pertama, akan
dibahas percabangan dengan if, else if, dan else. Bentuk percabangan ini adalah
yang paling sederhana. Format penggunaannya adalah:
if (ekspresi1)
pada sourcecode ini, pertama-tama, if akan mengecek
...
sourcecode1;
else if (ekspresi2)
...
sourcecode2;
else
...
sourcecode3;
ekspresi1
, jika benar maka sourcecode1;
akan dieksekusi, jika tidak, maka
eksekusi program berpindah ke else if (ekspresi2), else if
akan
mengecek ekspresi2
, jika ekspresi2
benar maka sourcecode2
akan dieksekusi, jika salah maka eksekusi program berpindah ke sourcecode3.
Pada format di atas, else if
sebenarnya opsional (boleh ada boleh
juga tidak), jika anda hanya memiliki dua alternatif "jawaban" maka
bagian else if
tidak perlu digunakan.
Selanjutnya adalah percabangan dengan switch
. switch
digunakan
untuk percabangan dengan banyak alternatif pilihan, sebenarnya hal ini juga
dapat dilakukan dengan if , else if , else
, tetapi switch lebih
efisien dan lebih mudah jika kita sudah menguasainya. Format penggunaannya adalah:
switch (ekspresi)
{case (ekspresi1):
...
sourcecode1;
break;
case (ekspresi2):
...
sourcecode2;
break;
case (ekspresi3):
...
sourcecode3;
break;
default:
...
sourcecode4;
break;
}
pada source code diatas, ekspresi
biasanya merupakan sebuah variabel
yang dapat memiliki banyak macam nilai (misalnya variabel bertipe int), nilai
ekspresi
akan dicocokkan (matching) dengan nilai ekspresi1
,
ekspresi2
dan ekspresi3
, jika ternyata ekspresi
bernilai sama dengan salah satunya maka source code dibawahnya akan dieksekusi,
misalnya: ekspresi
bernilai sama dengan ekspresi1
maka source code1
akan dieksekusi. Jika ternyata tidak ada nilai
yang sama dengan ekspresi
maka yng dieksekusi adalah source code
sesudah default
. default
dan pernyataan sesudahnya
adalah opsional (dapat digunakan, dan dapat tidak digunakan). Satu hal lagi,
setelah setiap alternatif pernyataan source code, anda harus menggunakan keyword
break;
, tujuannya adalah agar eksekusi program keluar dari percabangan
switch
sehingga source code yang ada di bawahnya tidak dieksekusi.
Misalnya: anda lupa memberikan break; setelah sourcecode2
, maka
saat ekspresi
bernilai sama dengan ekspresi2
eksekusi
program tidak akan keluar dari percabangan switch
setelah sourcecode2
tetapi akan terus melanjutkan eksekusi ke sourcecode3
, setelah
keyword break
(sesudah sourcecode3
) barulah eksekusi
keluar dari percabangan dengan switch ini. Hal ini tentunya bukan hal yang anda
harapkan, jadi teliti lah saat menggunakan switch. Masih ada bentuk percabangan
lain dalam C, tetapi tidak akan di bahas di sini sebab jarang digunakan dan
dengan kedua teknik percabangan yang ada di atas sudah cukup untuk sebagian
besar kasus yang kita hadapi sehari-hari.
Perulangan
Perulangan adalah teknik pemrograman yang digunakan untuk mengatasi
pernyataan program yang harus dikerjakan secara berulang-ulang. Perulangan dalam
C dapat diimplementasikan dengan beberapa keyword, yaitu for, do .. while
, while
. Ketiga keyword ini lah yang paling sering digunakan. Perulangan
menggunakan for
mempunyai format sebagai berikut.
for( ekspresi1 ; ekspresi2 ;ekspresi3 )
pada source code di atas
{
...
source_code_ulang;
}
ekspresi1
adalah ekspresi yang
akan dievaluasi pertama kali , kemudian ekspresi2
dievaluasi, jika
nilainya benar maka source_code_ulang;
dieksekusi, setelah itu
ekspresi3
akan dieksekusi, kemudian jalannya program kembali lagi
ke pernyataan for dan ekspresi2
kembali dievaluasi (ekspresi1
tidak lagi dieksekusi), jika benar maka source_code_ulang;
dieksekusi,
setelah itu ekspresi3
akan dieksekusi, kemudian jalannya program
kembali lagi ke pernyataan for
, hal ini dilakukan berulang-ulang
selama ekspresi2
masih bernilai benar. Berikut contoh potongan
programnya:
for(int i = 0; i < 4 : i++)
{
printf("loop masih dieksekusi");
}
pada source code di atas, ekspresi1
adalah int i = 0 , ekspresi
ini mendeklarasikan sebuah variabel bertipe int dan memberikan variabel tersebut
nilai 0. Ekspresi ini hanya satu kali dieksekusi, yaitu saat keyword for pertama
kali dipanggil. Kemudian nilai i dievaluasi apakah kurang dari 4 (ekspresi2
adalah i < 4
), saat pertama kali dieksekusi ekspresi ini bernilai
benar sebab i < 4
, kemudian kita memanggil fungsi printf("..")
untuk menampilkan tulisan ke layar, setelah itu nilai i ini dinaikkan 1 satuan
menjadi 2 pada ekspresi3
(ekspresi3
adalah i++
),
langkah-langkah ini diulang sampai nilai i menjadi 4. Saat i = 3,
pengulangan terakhir akan membuat nilai i menjadi 4 (sebab ekspresi3
di eksekusi), jalannya program kembali ke keyword for
, kemudian
ekspresi2
dieksekusi, sekarang ekspresi tersebut bernilai salah
sebab i = 4 , sehingga eksekusi program akan keluar dari perulangan ini, dan
melanjutkan ke source code sesudah kurung kurawal penutup pada contoh di atas.
Perulangan dengan while mempunyai format sebagai berikut: while( test_expression
)
{
...
source_code;
}
pada source code di atas, pertama-tama keyword while
dipanggil,
kemudian test_expression dievaluasi, jika nilainya benar, maka source_code
akan dieksekusi sampai test_expression
bernilai salah. Jika test_expression
bernilai salah maka jalannya program akan melewati perulangan ini begitu saja
(melanjutkan ke source code sesudah kurung kurawal penutup pada contoh di atas).
Berikut ini source code yang menggunakan while dan mengerjakan hal yang sama
dengan source code untuk perulangan menggunakan for.
int i = 0;
Perulangan dengan
while(i < 4)
{
i++;
printf("loop masih dieksekusi");
}do...while
, pada dasarnya sama dengan
perulangan dengan while, hanya saja pernyataan yang ada di antara keyword do
dan while akan dieksekusi minimal satu kali, sebab pengetesan ekspresi dilakukan
setelah peryataan tersebut dieksekusi. Berikut ini source code yang menggunakan
do...while
dan mengerjakan hal yang sama dengan source code untuk
perulangan menggunakan for. int i = 0;
do
{
printf("loop masih dieksekusi");
i++;
}
while(i < 4)
Obrolan bebas:
"Sebelum anda bosan, penulis ingin memberitahukan bahwa bahasa C diciptakan oleh seorang hacker bernama Dennis Ritchie yang bekerja di Bell Telephone Laboratories pada 1972. Compiler C yang mendukung paling banyak arsitektur komputer saat ini adalah GNU C yang terdapat di banyak varian sistem operasi UNIX, termasuk Linux. Ada beberapa implementasi bahasa C yang mungkin akan membuat anda tertarik, yaitu: kernel dari sistem operasi Linux, karena source code-nya free, anda dan juga penulis bisa belajar banyak dari sana, kemudian, saat ini sudah ada beberapa cellphone yang dapat diisi dengan program aplikasi yang dibuat dengan bahasa C, dan yang terpenting adalah 'kekuatan' dari software apapun yang anda buat hanya dibatasi oleh imajinasi anda :)."
Fungsi dan Struktur Program C
Komponen utama bahasa C adalah sebuah bentuk yang dikenal sebagai fungsi.
Program C yang paling sederhana sekalipun harus mempunyai minimal sebuah fungsi,
yaitu fungsi main()
. Fungsi adalah sekelompok perintah (bahasa
C) yang dapat dipanggil untuk mengerjakan sebuah pekerjaan tertentu. Format
dasar "definisi" sebuah fungsi adalah:
return_type function_name(param1_type parameter1, param2_type parameter2, ...)
{
...
source_codes ;
return hasil_perhitungan;
}
Format dasar "deklarasi" sebuah fungsi adalah:
return_type function_name(param1_type parameter1, param2_type parameter2, ...);
Catatan:
Sebenarnya ada beberapa hal yang perlu anda perhatikan pada bentuk umum di atas, tanda...
padareturn_type function_name(param1_type parameter1, param2_type parameter2, ...)
maksudnya adalah sebuah fungsi dapat mempunyai lebih dari dua parameter, tergantung bagaimana anda mendeklarasikan dan mendefinisikan sebuah fungsi. Anda perlu berhati-hati dengan sintaks ini, sebab dalam bahasa C sebuah fungsi yang dideklarasikan atau didefinisikan dengan...
sebagai parameter terakhirnya berarti fungsi tersebut dapat memiliki jumlah parameter yang "tidak terbatas", misalnya fungsi yang dideklarasikan sebagai berikut:
void test(int a, char b , ...);
fungsi ini dapat menerima 2 atau lebih parameter, dan jumlah parameter tersebut terserah anda saat memanggil fungsi tersebut, asal lebih dari atau sama dengan 2. Dalam tutorial ini kita tidak akan mempelajari fungsi seperti ini, kita hanya akan mempelajari fungsi dengan jumlah parameter yang pasti.
return_type
adalah tipe data dari hasil_perhitungan
.
hasil_perhitungan
dalam literatur biasa disebut "return
value". Sebuah fungsi dapat mengembalikan (menghasilkan) sebuah nilai
(dalam fungsi di atas nilai yang dikembalikan adalah hasil_perhitungan
),
namun dapat juga tidak mengembalikan apa-apa. Jika sebuah fungsi tidak mengembalikan
nilai apa pun maka return_type
harus bertipe void dan fungsi tersebut
tidak boleh mengandung keyword return sebuah_nilai
, dengan sebuah_nilai
adalah nilai tertentu, sekalipun nilai itu adalah 0. parameter1
dan parameter2
adalah nilai input yang akan diolah oleh fungsi. function_name
adalah nama dari fungsi yang kita definisikan. param1_type
dan param2_type
adalah tipe data dari parameter1
dan parameter2
. Sebuah
fungsi juga boleh tidak mempunyai parameter sama sekali, fungsi tanpa parameter
adalah valid bagi compiler C. Untuk membuat sebuah program C yang menggunakan
fungsi (selain fungsi main()
)dapat berjalan dengan baik, anda harus
menempatkan "definisi" fungsi tersebut sebelum fungsi main()
atau jika anda tidak ingin menaruh definisi fungsi tersebut sebelum main()
maka letakkan "deklarasi" fungsi tersebut sebelum main()
seperti pada contoh yang akan di bahas. Hal ini diperlukan sebab fungsi yang anda
buat kemungkinan tidak akan dikenali jika anda tidak mendeklarasikannya atau mendefinisikannya
sebelum fungsi main()
. Selanjutnya kita akan membedah sebuah program
C yang sangat sederhana untuk memahami konsep di atas.
#include < stdio.h >
#define UINT unsigned int
/* deklarasi fungsi kali */
UINT kali(UINT a, UINT b);
void main()
{
UINT faktor1;
UINT faktor2;
UINT hasil ;
printf("Ketikkan nilai untuk faktor1 ");
scanf("%d", &faktor1);
if(faktor1 >= 0 && faktor1 <= 9 )
{
printf("Ketikkan nilai untuk faktor2 ");
scanf("%d", &faktor2);
if(faktor2 >= 0 && faktor2 <= 9)
{
printf("faktor1 = %d ; faktor2 = %d \n", faktor1, faktor2);
hasil = kali(faktor1, faktor2);
printf("Hasil perkalian keduanya adalah %d\n", hasil);
}
else
{
printf("Anda mengetikkan karakter bukan bilangan \n");
return;
}
}
else
{
printf("Anda mengetikkan karakter bukan bilangan \n");
return;
}
}
/* definisi fungsi kali */
UINT kali(UINT a, UINT b)
{
UINT c = a*b;
return c;
}
contoh output dari program di atas adalah: Ketikkan nilai untuk faktor1 2
Ketikkan nilai untuk faktor2 3
faktor1 = 2 ; faktor2 = 3
Hasil perkalian keduanya adalah 6
sebelum membahas source code di atas, akan dijelaskan bagaimana cara membuat program dengan source code di atas pada Microsoft Visual C++ 6.
- Buka menu File|New pada Microsoft Visual C++ 6, kemudian pilih tab Projects
dan pilih Win32 Console Application. Pada bagian Project Name ketikkan nama
project yang anda inginkan, kemudian klik OK.
- Pilih An empty project pada dialog yang muncul kemudian klik Finish, pada dialog baru yang muncul klik OK.
- Sekarang anda telah memiliki sebuah project yang masih kosong, untuk membuat
dari source code program di atas, buka kembali menu File|New dan pilih tab
Files, kemudian pilih C++ Source File, pastikan check box Add to project dalam
keadaan di-cek, dan nama project yang muncul pada kotak di bawahnya adalah
nama project anda tadi. Kemudian ketikkan nama source file yang anda inginkan
dengan extensi .c pada kotak file name, sebab Visual C++ akan secara otomatis
memberi extensi .cpp jika anda tidak memberikan extensi pada nama file yang
anda masukkan.
- Kemudian copy source code di atas ke file yang baru anda buat.
- Compile file tsb dengan menekan Control+F5 atau memilih menu Build|Execute "nama file".exe . Sekarang anda sudah dapat melihat outputnya pada sebuah console windows (dos prompt pada windows 98).
Sekarang kita akan membahas arti dari source code di atas. Pada baris pertama anda melihat
Catatan:
Sebenarnya anda juga dapat menjalankan program yang dihasilkan dalam mode debug dengan menekan F5 atau memilih menu Build|Start Debug|Go, namun anda tidak akan sempat melihat outputnya.
#include
<stdio.h>
, baris ini maksudnya
adalah preprocessor harus menempatkan isi dari file tersebut diawal file yang
kita buat sebelum compiler mengolahnya. stdio.h adalah file yang disebut sebagai
file header (extensi *.h) yang mengandung definisi fungsi-fungsi yang kita butuhkan
untuk menampilkan program kita (fungsi printf( , )
) dan fungsi-fungsi
untuk mengambil data yang dimasukkan user melalui keyboard (fungsi scanf
( , )
). Setelah itu pada baris selanjutnya, kita mendefinisikan tipe data
unsigned int dengan nama yang lebih singkat yaitu UINT. Kemudian, fungsi int
kali(int a, int b)
dideklarasikan. Pada deklarasi sebuah fungsi, yang dibutuhkan
sebenarnya hanya tipe data yang dikembalikan oleh fungsi (tipe data return
value), nama fungsi tersebut dan tipe data semua parameternya, jadi a dan
b sebenarnya tidak dibutuhkan, kita memberikan simbol ini hanya untuk mempermudah
saat membaca source code. Kemudian pada baris selanjutnya fungsi main didefinisikan7.
Fungsi main adalah fungsi yang pertama kali dipanggil oleh sistem operasi saat
program kita dimulai8. Pada contoh di atas, main didefinisikan sebagai
fungsi yang tidak punya kembalian ( return value),
sebab keyword void berarti tidak ada return value sama sekali. Fungsi main juga
didefinisikan tidak mempunyai parameter. Pada baris selanjutnya kita mendefinisikan
3 buah variabel unsigned int, kemudian ada sebuah ekspresi yang diuji: if (faktor1 >= 0 && faktor1 <=9)
pernyataan ini mengetes input yang diberikan user (yang kita peroleh dari fungsi
scanf
), jika inputnya adalah nilai numerik antara 0 dan 9 maka blok
pernyataan di bawah pernyataan if tersebut akan dieksekusi, jika tidak maka blok
pernyataan tersebut akan dilewati, dan program kita akan mengeksekusi blok pernyataan
sesudah else. Blok pernyataan ini akan menampilkan pesan kemudian mengakhiri program
dengan pernyataan return;9. Jika input dari user memenuhi syarat percabangan
if yang pertama maka user kembali diminta memberikan input karakter dari keyboard,
input ini kemudian diuji dengan ekspresi: if (faktor2 >= 0 && faktor2 <=9)
Jika input tersebut memenuhi syarat percabangan di atas (ekspresi sesudah if bernilai benar) maka input dari user akan ditampilkan, kemudian fungsi UINT kali(UINT , UINT) akan dipanggil dengan parameter-parameternya adalah input dari user (
faktor1
dan faktor2
) dan hasil yang diperoleh dari fungsi tersebut dipindahkan
ke variabel yang diberi nama hasil, isi dari variabel ini kemudian ditampilkan
pada baris selanjutnya. Jika ternyata input kedua dari user tidak memenuhi syarat
maka program akan mengeksekusi blok pernyataan sesudah keyword else yang ada di
dalam blok pernyataan sesudah if (faktor1
>= 0 && faktor1 <=9)
, sehingga program akan menampilkan
pesan kesalahan dan program kemudian diakhiri.
catatan:
7 Anda harus membedakan antara definisi dan deklarasi sebuah fungsi, deklarasi sebuah fungsi digunakan untuk memberikan "tanda pengenal" bagi sebuah fungsi agar fungsi tersebut dapat dikenali saat dipanggil, definisi sebuah fungsi adalah "ungkapan teknis" tentang apa yang harus dilakukan oleh sebuah fungsi saat fungsi tersebut dipanggil.
8 Fungsi semacam ini disebut entry point. Sebenarnya anda dapat memberikan entry point yang bukan fungsimain()
pada Microsoft Visual C++ dengan mengatur salah satu switch pada linker-nya dan memberikan nama fungsi yang anda inginkan pada parameter yang diminta oleh switch tersebut untuk dijadikan entry point.
9 Perhatikan bahwa pernyataan return ; dan pernyataan return 0 ; adalah berbeda, pernyataan yang pertama mengakhiri sebuah pemanggilan fungsi tanpa mengembalikan nilai apa-apa, sedangkan pernyataan yang kedua mengembalikan nilai yaitu 0.
Kata-kata yang dibatasi oleh karakter /* dan */ pada source code di atas adalah komentar (comment) yang berguna sebagai dokumentasi source code, kata-kata yang ada di antara karakter ini akan diabaikan oleh compiler sehingga ada atau tidaknya kata-kata tersebut tidak berpengaruh
Fungsi untuk Input dan Output
Pada tutorial ini ada 2 buah fungsi input dan output yang digunakan, penulis
akan menjelaskannya agar anda tidak bingung saat mempelajari source code yang
diberikan. Fungsi yang pertama adalah fungsi output, yaitu fungsi yang digunakan
untuk menampilkan hasil yang diperoleh dari program kita ke layar komputer,
fungsi tersebut adalah int printf(const char* [format],[argument]...)
.
Anda tidak perlu bingung menggunakan fungsi yang agak kompleks ini, anda cukup
mengganti [format]
dengan petik ganda dan di antara petik ganda
tersebut anda tuliskan kata-kata yang ingin ditampilkan. [argument]
adalah
opsional. Berikut ini contoh pengunaannya untuk menampilkan sebuah bilangan
desimal:
int test = 10;
pada source code di atas, di antara tanda petik ganda diketikkan kata-kata
yang ingin ditampilkan. Tanda
printf("inilah bilangan yang ingin ditampilkan: %d",test);
%d
adalah sebuah kode khusus untuk
memberitahukan fungsi printf bahwa kita ingin menampilkan bilangan integer (untuk
tanda yang harus digunakan silahkan lihat dokumentasi compiler yang anda gunakan,
untuk Visual C++ gunakan MSDN) pada posisi tersebut, bilangan yang ingin kita
tampilkan kemudian diletakkan pada parameter [argument]
dari fungsi
printf. Jika anda ingin menggunakan lebih banyak angka lagi (yang berbeda posisinya
dan dipisahkan karakter lain) maka anda cukup menmbah lagi [argument]
dan meletakkan penanda seperti %d
di atas pada posisi yang
anda inginkan.
Fungsi yang kedua adalah int scanf(const char* [format], [argument]...)
.
Fungsi ini adalah fungsi input, yang kita gunakan untuk menerima input dari
user melalui keyboard. Cara penggunanya mirip dengan fungsi printf, bedanya
terletak pada parameter kedua, parameter [argument] d
ari fungsi
ini adalah alamat dari variabel yang ingin kita isi. Parameter pertama ([format]
),
biasanya adalah tanda petik ganda yang diantaranya diisi dengan kode jenis varabel
yang akan kita isi pada parameter kedua ([argument]
). Contoh:
int nilai1;
scanf("%d", &nilai1);
pada source code di atas, kita mengambil input dari user yang bertipe integer,
sehingga kode yang digunakan adalah %d
. Parameter kedua scanf adalah
alamat dari variabel yang kita miliki sehingga digunakan address of operator10
untuk mengambil alamat dari variabel tersebut. Jadi, secara keseluruhan, kita
mengambil input dari user dan memasukkannya ke variabel nilai1.
10 Address of operator akan dijelaskan lebih rinci pada bagian pointer.
Pointer dan Array
Pointer adalah salah satu konsep yang paling sulit dipahami oleh programer
pemula dalam bahasa C dan C++. Untuk memahami dengan baik bagaimana pointer
bekerja, anda harus bisa membayangkan pergerakan variabel-variabel antara microprocessor
dan memori utama, dan akan lebih baik jika anda tahu bagaimana sistem operasi
semacam windows mengorganisasikan memori. Semoga hal ini tidak membuat anda
merasa rendah diri. Penulis sendiri yang pada awalnya adalah "hardware
mania" sebelum memulai belajar programming merasakan bahwa konsep ini lebih
mudah untuk dipahami, dibandingkan orang-orang yang tidak mengerti hardware
dengan baik.
Pointer pada hakikatnya adalah variabel yang menyimpan alamat variabel lain
yang tipe data-nya sama dengan tipe data pointer tersebut. Untuk mempermudah
memahami pointer, terlebih dahulu akan dijelaskan bagaimana pengorganisasian
memori dalam sebuah komputer oleh sistem operasi yang berjalan pada komputer
tersebut. Sistem operasi modern umumnya menggunakan apa yang dikatakan swap
file atau page file. Page file adalah bagian dari media penyimpan
eksternal (diluar motherboard) yang diperlakukan seperti RAM. Dalam sistem operasi
seperti ini setiap program mempunyai wilayah memori sendiri (memory space) yang
hanya dapat digunakan oleh program itu sendiri, tidak dapat digunakan oleh program
lain. Memory space ini adalah bagian dari RAM namun bisa juga sebagian berada
pada swap file, hal ini diatur oleh bagian manajemen memori dari sistem operasi.
Wilayah memory ini dibagi ke dalam beberapa komponen lagi, yaitu:
- Global namespace, bagian ini adalah bagian memory space yang dapat diakses
oleh program yang kita miliki dengan bebas.
- Code Space, bagian ini menyimpan kode program yang akan dieksekusi (machine
code) yang kita hasilkan dari source code program yang sudah di-compile..
Bagian ini adalah bagian yang read only, sebab kita hanya dapat mengeksekusi
sebuah program, bukan merubah kode program itu sendiri saat dia dijalankan.
- Stack, yaitu bagian dari memory yang berfungsi untuk menyimpan data sementara,
saat sebuah fungsi sedang dipanggil dan diproses.
- Register, yaitu bagian dari Microprocessor yang kita miliki yang dapat
menyimpan data sementara ketika program sedang dijalankan11. Register-register
inilah yang memelihara keadaan sistem kita agar tetap berjalan sebagaimana
mestinya. Tentang register akan dijelaskan lebih lanjut pada pemrograman assembly.
Yang perlu kita ketahui saat ini adalah beberapa register yang bertugas menyimpan
alamat kode program yang sedang dieksekusi saat ini, register ini disebut
sebagai instruction pointer, selain itu ada register yang berfungsi
menunjukkan alamat stack yang digunakan saat ini, register ini disebut stack
pointer dan ada pula register yang bertugas menyimpan sementara alamat
yang dimiliki oleh stack pointer, register ini disebut stack frame.
- Free store, adalah bagian memory di luar bagian memory yang telah di sebut di atas.
Sebelum melangkah lebih jauh, akan dijelaskan apa yang terjadi ketika sebuah fungsi dipanggil. Berikut ini adalah proses yang terjadi :
11 Register adalah bagian inti dari microprocessor yang kita miliki, disinilah aktivitas utama yang terjadi saat komputer kita jalankan. Pada x86 32 bit ada beberapa register yang digunakan secara intensif dan ada juga yang tidak. Penjelasan lebih rinci tentang register-register ini dapat anda lihat pada tutorial pemrograman assembly.
- Pertama, alamat kode instruksi yang ditunjuk oleh instruction pointer dinaikkan
satu instruksi, sehingga instruction pointer menunjuk ke alamat kode instruksi
yang akan dieksekusi setelah fungsi yang dimaksud selesai dieksekusi. Alamat
ini kemudian di simpan pada stack.
- Kemudian, nilai stack pointer dinaikkan untuk menyediakan ruang bagi return
value dari fungsi yang akan kita eksekusi.
- Alamat kode instruksi pertama dari fungsi yang ingin kita eksekusi kemudian
di pindahkan ke instruction pointer, sehingga fungsi tersebut adalah perintah
yang akan dieksekusi selanjutnya.
- Alamat stack saat ini yang ada pada register stack pointer kemudian di
copy ke register stack frame pointer, mulai saat ini, data apapun yang dimasukkan
ke stack dianggap lokal terhadap fungsi yang kita panggil.
- Seluruh parameter fungsi kemudian disimpan atau tepatnya di copy ke stack
- Instruction pointer kemudian mengeksekusi kode instruksi pertama yang telah
ditunjuk pada langkah 3, sehingga fungsi dieksekusi.
- Variabel lokal yang didefinisikan di dalam fungsi di simpan di stack, misalnya
variabel c pada fungsi kali pada source code sebelumnya.
- Fungsi telah selesai dieksekusi, hasil perhitungan dari fungsi tersebut
kemudian diletakkan pada bagian stack yang telah disediakan pada langkah 2.
- Alamat yang disimpan oleh register stack frame pointer kemudian di restore
kembali ke register stack pointer, sehingga stack efektif saat ini tidak mengandung
parameter fungsi dan variabel yang lokal terhadap fungsi.
- Return value dari fungsi di pindahkan ke variabel yang menerima hasil fungsi
tersebut.
- Isi instruction pointer yang di simpan di stak pada langkah 1 kemudian
dipindahkan ke instruction pointer , sehingga program kembali dilanjutkan.
Setelah membaca uraian di atas kita tahu bahwa ada beberapa alasan mengapa
pointer diperlukan, yaitu:
- Dalam kasus tertentu kita perlu mengubah isi dari sebuah variabel yang
sebenarnya, kita tidak bisa melakukan hal ini dengan fungsi seperti yang telah
kita pelajari sebab fungsi tersebut hanya memanipulasi "kopian"
dari variabel yang sebenarnya ingin kita manipulasi (yang di simpan di stack
ketika fungsi tersebut diproses )
- Jika kita bisa merubah secara langsung variabel yang ingin kita manipulasi
maka hal itu adalah lebih efisien dibandingkan mengkopi dan memanipulasinya,
kemudian mengkopi kembali hasil manipulasi tersebut dari stack ke variabel
aslinya.
- Dalam kasus sebuah fungsi yang harus mengembalikan 2 atau lebih return
value. Jelas hal ini tidak mungkin dilakukan dengan metode yang telah kita
ketahui, sebab sebuah fungsi hanya mungkin mempunyai 1 return value (baca
kembali bagian fungsi).
Berikut ini adalah sintaks untuk mendeklarasikan pointer:
data_type * pointer_name;
data_type
adalah tipe data dari variabel yang ditunjuk oleh pointer
tersebut, pointer_name dalah nama dari pointer yang kita inginkan. Contoh:
int * pInt;
pada contoh ini dideklarasikan sebuah pointer ke sebuah variabel bertipe int.
Sebuah pointer harus menunjuk ke alamat memory tertentu, jika tidak maka pointer
tersebut bisa menimbulkan bug, pointer seperti ini disebut stray pointer.
Dengan demikian jika anda belum mengisi sebuah pointer dengan alamat memori
tertentu maka inisialisasi pointer tersebut dengan nilai 0 sehingga pointer
tersebut menjadi null pointer . Null pointer adalah pointer yang tidak
berbahaya, sehingga tidak akan menimbulkan bug seperti stray pointer. Untuk
itu contoh di atas harus diperbaiki sebagai berikut:
int * pInt = 0;
Beberapa operator dalam C yang bisa punya banyak makna dan kegunaan, tergantung
konteks penggunaannya. Operator jenis ini antara lain & dan * . Berikut
adalah contoh untuk memperjelas:
int A = 9;
int B = 3;
int C;
int * pA = &A ; /* operasi 1 */
C = A*B; /* operasi 2 */
C = A & B; /* operasi 3 */
*pA = C; /* operasi 4 */
Pada baris /* operasi 1 */, pernyataan: int * pA = &A mempunyai arti: kita
mendeklarasikan pA sebagai pointer ke variabel bertipe int, hal ini diwakili
oleh sintaks int * pA , kemudian pointer ini kita buat menunjuk ke alamat variabel
A. Alamat variabel A ditunjukkan dengan sintaks &A, operator & pada
sintaks ini disebut sebagai adddress of operator, karena memberikan alamat variabel
yang ada di sebelah kanannya yaitu alamat variabel A. Alamat ini kemudian kita
pindahkan ke variabel pA yang baru dideklarasikan dengan operator = . Pada baris
/* operasi 2 */, operator * berfungsi sebagai operator kali biasa, mengalikan
variabel di sebelah kiri dan kanannya. Pada baris /* operasi 3 */, operator
& juga berfungsi sebagai operator bitwise AND biasa (sebab fungsi dasarnya
memang seperti itu), yaitu melakukan operasi bitwise AND antara variabel di
sebelah kiri dan kanannya. Pada baris /* operasi 4 */, operator * disebut sebagai
indirection operator sebab pada ekspresi *pA operator ini bekerja mengakses
isi dari alamat memori yang ditunjukkan oleh pointer pA, jadi pernyataan : *pA
= C mempunyai arti, pindahkan isi variabel C ke variabel yang ditunjuk oleh
pointer pA, atau dengan kata lain, pindah isi variabel C ke varabel A (sebab
pA menunjuk ke alamat memory variabel A).
Selanjutnya kita akan membahas sebuah contoh program yang menggunakan pointer.
#include < stdio.h >
#define UINT unsigned int
void tukar1(UINT* pVar1, UINT* pVar2);
void tukar2(UINT Var1, UINT Var2);
int main()
{
UINT var1 = 4;
UINT var2 = 8;UINT * pVar1 = &var1;
printf("nilai var1 saat ini : %d \n", var1);
printf("nilai var2 saat ini : %d \n", var2);
tukar2 (var1,var2);
printf("nilai var1 setelah tukar2: %d \n",var1);
printf("nilai var2 setelah tukar2: %d \n",var2);
tukar1(pVar1, &var2);
printf("nilai var1 setelah tukar1: %d \n", var1);
printf("nilai var2 setelah tukar1: %d \n", var2);
return 0;
}void tukar2(UINT Var1, UINT Var2)
{
/* karena fungsi ini passing by value maka yang kita
rubah nilainya saat ini adalah copy variabel var1 dan var2
yang ada di stack */
UINT temp;
temp = Var1; /* copy isi var1 pada temp */
Var1 = Var2; /* copy isi var2 ke var1 */
Var2 = temp; /* copy isi temp ke var2 */
}void tukar1(UINT* pVar1, UINT* pVar2)
{
UINT temp;
temp = (*pVar1); /* copy isi var1 pada temp*/
(*pVar1) = (*pVar2); /* copy isi var2 ke var1 */
*pVar2 = temp; /* copy isi temp ke var2 */
}
Jika anda mengeksekusi program di atas, akan diperoleh output sebagai berikut:
nilai var1 saat ini : 4
Dari output ini tampak jelas bahwa fungsi tukar1 benar-benar mempertukarkan
isi dari var1 dan var2 , sementara fungsi tukar2 tidak demikian. Di sini lah
kegunaan pointer mulai tampak. Pada fungsi tukar1, parameter-parameter yang
digunakan adalah alamat variabel-variabel yang ingin kita rubah. Anda juga telah
membaca sebelumnya tentang bagaimana sebuah fungsi bekerja, dari uraian tsb
diketahui bahwa saat kita tidak menggunakan pointer sebagai parameter fungsi
(biasa disebut passing by value) maka yang diubah bukan parameter yang
kita masukkan, melainkan "kopian" dari parameter tersebut yang ada
di stack, sehingga pada saat fungsi selesai dieksekusi parameter semula tidak
berubah. Tidak demikian hal nya jika kita menggunakan pointer sebagai parameter
(biasa disebut passing by pointer), karena yang kita jadikan paramater
adalah alamat dari variabel tersebut, maka saat kita menggunakan indirection
operator, variabel yang kita peroleh adalah variabel aslinya, and dapat melihat
aplikasinya pada fungsi tukar1. Fungsi ini tidak penulis jelaskan lebih jauh
sebab comment yang diberikan serta penjelassan sintaks pointer sebelumnya penulis
anggap sudah memadai. Karena keterbatasan waktu, penjelasan tentang pointer
hanya sampai di sini.
nilai var2 saat ini : 8
nilai var1 setelah tukar2: 4
nilai var2 setelah tukar2: 8
nilai var1 setelah tukar1: 8
nilai var2 setelah tukar1: 4
Sekarang kita akan membahas mengenai Array. Array adalah sekelompok variabel
yang berbeda, dengan tipe data yang sama yang diperlakukan sebagai satu variabel
dan dapat diakses dengan menggunakan nama dari array tersebut. Sintaks untuk
mendeklarasikan array adalah:
tipe_data nama_array[jumlah_komponen];
tipe_data
adalah tipe data dari variabel yang menjadi komponen
array tersebut. nama_array
adalah nama yang kita berikan untuk
array tersebut. jumlah_komponen
adalah jumlah komponen (variabel)
yang dimiliki oleh array tersebut. Berikut ini contoh penggunaannya:
#include < stdio.h >
#define UINT unsigned intvoid main()
{UINT komponen = 5;
UINT arr_Test[10];
UINT c; for( c = 0 ; c < 10 ; c++)
{
arr_Test[c] = komponen;
printf("Isi dari komponen ke[%d], adalah: %d \n",c,arr_Test[c]);
komponen++;
}}
output dari program di atas adalah:
Isi dari komponen ke[0], adalah: 5
Isi dari komponen ke[1], adalah: 6
Isi dari komponen ke[2], adalah: 7
Isi dari komponen ke[3], adalah: 8
Isi dari komponen ke[4], adalah: 9
Isi dari komponen ke[5], adalah: 10
Isi dari komponen ke[6], adalah: 11
Isi dari komponen ke[7], adalah: 12
Isi dari komponen ke[8], adalah: 13
Isi dari komponen ke[9], adalah: 14
Pada program di atas, kita mendeklarasikan array dengan tipe data unsigned int,
dengan jumlah komponen sebanyak 10 buah. Kemudian komponen-komponen dari array
tersebut diisi dengan nilai variabel komponen dalam sebuah perulangan menggunakan
for. Perlu anda ketahui bahwa setiap komponen array dapat diakses dengan sebuah
nilai yang disebut indeks. Indeks menunjukkan letak komponen tersebut
di dalam array. Indeks paling kecil adalah 0, yaitu indeks yang menunjukkan
komponen pertama dari array. Indeks 1 menunjukkan komponen ke-2, dan seterusnya.
Perhatikan bahwa untuk mengakses komponen pertama dari array arr_Test
,
digunakan sintaks arr_Test
[0] (sebab nilai awal variabel c adalah
0), dan seterusnya. Satu hal yang perlu anda waspadai saat menggunakan array,
yaitu pastikan bahwa anda tidak mengakses indeks array yang melebihi jumlah
komponen array tersebut, sebab hal ini akan menjadi bug pada program anda. Misalnya
anda mendeklarasikan sebuah array mempunyai 9 komponen, maka indeks maksimum
yang dapat anda gunakan adalah nama_array[8], sebab indeks dimulai dari 0.
Nama sebuah array sebenarnya merupakan pointer ke elemen pertama dari array
tersebut, sehingga anda dapat mengakses elemen array tesebut dengan sebuah trik
yang disebut dengan pointer arithmetic. Trik ini memanfaatkan nama array yang
diperlakukan sebagai pointer. Berikut contohnya: (program sebelumnya diubah
untuk memberikan output yg sama)
#include < stdio.h >
#define UINT unsigned intvoid main()
Output yang dihasilkan oleh program ini sama dengan output program sebelumnya.
Perhatikan bahwa untuk menunjuk dan memanipulasi komponen dari
{
UINT komponen = 5;
UINT arr_Test[10];
UINT* pArr = arr_Test;
UINT c;
for( c = 0 ; c < 10 ; c++)
{
*pArr = komponen;
printf("Isi dari komponen ke[%d], adalah: %d \n",c,arr_Test[c]);
pArr++;
komponen++;
}
}
arr_Test
kita menggunakan pointer pArr
. Sintaks pArr++
artinya
naikkan nilai alamat memori yang ditunjukkan pArr
satu satuan,
satu satuan di sini adalah sejumlah byte dari ukuran tipe data pointer tersebut,
karena tipe datanya integer maka alamat yang ditunjuk pointer tersebut dinaikkan
sebanyak 4 byte (pada windows). Menaikkan nilai pArr
satu satuan
sama saja artinya dengan menunjuk ke komponen selanjutnya dalam sebuah array,
inilah yang disebut pointer arithmetic. Komponen pembentuk sebuah array diletakkan
pada alamat yang berurutan di memory, hal inilah yangmenyebabkan kita dapat
melakukan pointer arithmetic. Sampai di sini anda telah mengerti tentang pointer
dan array, tidak terlalu sulit bukan ?
Obrolan bebas:
"Anda mungkin sudah lelah mengikuti tutorial ini, ... apalagi penulis :(. Jadi... kita break sebentar. Penulis ingin ngobrol sesuatu soal programming. Mungkin anda mengenal John Carmack, programer yang ada di balik ID Software, pembuat game Quake III Arena, RTCW (Return to The Castle Wolfenstein) dan Doom III. Kadang penulis berpikir, bahasa dan tool seperti apa yang digunakan John Carmack untuk membuat game-nya ? sebab sebagian besar atau malah semua game buatan ID software tersedia untuk hampir semua platform komputer, mulai dari Apple sampai PC. Kalau kita pikir, pastilah 'code base' dari semua game itu sama, tidak mungkin dia membuat game dengan 'code base' yang berbeda-beda, sebab biaya pengembangannya akan sangat mahal. Jadi ... apa jawabannya ? "
Struct dan Union
Struct adalah bentuk yang sangat penting anda pahami jika anda ingin memprogram
menggunakan C pada sistem operasi windows, sebab sebagian besar komponen windows
mulai dari user interface sampai driver menggunakan bentuk ini, dan sebagian
lagi memanfaatkan union. Pada bagian sebelumnya kita telah mengenal bentuk penyimpanan
data dalam C yaitu dengan menggunakan variabel dan dengan menggunakan array.
Sekarang kita akan mempelajari metode penyimpanan data yang lebih umum yaitu
struct dan union. Pada penyimpanan data dengan array yang kita pelajari sebelumnya,
kita hanya dapat menyimpan data dengan menggunakan 1 macam tipe data yaitu tipe
data dari array tersebut. Penyimpanan data menggunakan struct memberi kita fleksibilitas
yang lebih baik, karena kita dapat menyimpan data dengan tipe data yang berbeda
ke dalam 1 struct. Struct pada dasarnya merupakan sebuah tipe data baru yang
didefinisikan oleh programer yang membuatnya. Sintaks yang digunakan untuk mendeklarasikan
sebuah struct adalah:
struct nama_struct{
tipe_data elemen_1;
tipe_data elemen_2;
...
tipe_data elemen_n;}variabel_struct1,variabel_struct2,..
;
pada sintaks di atas, tipe_data adalah tipe data dari elemen struct, yang telah
dikenali secara internal oleh compiler, misalnya int. Pada sintaks di atas,
variabel_struct1
dan variabel_struct2
adalah variabel
dengan tipe data struct nama_struct, variabel ini tidak wajib, jika anda lebih
suka membuat variabel dengan tipe data struct ini di tempat lain, anda dapat
membuatnya pada baris program sesudah deklarasi struct ini. Anda harus menggunakan
sintaks:
struct nama_struct variabel_struct;
Anda dapat menggunakan sintaks yang mempersingkat pembuatan variabel bertipe
struct tertentu dengan menggunakan keyword typedef12. Dengan keyword
ini, anda tidak perlu lagi memberikan kata struct di depan nama struct tersebut.
Sintaksnya sbb:
typedef struct nama_struct{
tipe_data nama_elemen_1;
tipe_data nama_elemen_2;
...
tipe_data nama_elemen_n;}nama_alias,
*nama_alias_pointer;
dengan teknik ini anda tinggal menggunakan sintaks:
nama_alias variabel_struct;
untuk membuat sebuah variabel dengan tipe-data struct nama_struct
.
Pada sintaks di atas *nama_alias_pointer
adalah opsional, keyword
ini dapat anda gunakan jika anda ingin membuat variabel bertipe pointer ke struct
nama_struct
. Untuk melakukannya, anda tinggal menggunakan sintaks:
nama_alias_pointer pointer_struct;
Sintaks seperti ini banyak digunakan dalam pemrograman C pada windows. Untuk
mengakses isi dari elemen sebuah struct anda dapat menggunakan member of operator
, yaitu: .
(titik). Misalnya untuk mengakses elemen_1
pada variabel variabel_struct1
anda akan menggunakan sintaks:
variabel_struct1
.elemen_1
jika variabel yang anda miliki ternyata adalah sebuah pointer, misalnya: pointer_struc
maka untuk mengakses elemennya, anda harus menggunakan member of operator dalam
bentuk lain, yaitu ->
. Sebagai contoh, untuk mengakses elemen_2
pada variabel pointer_struct anda akan menggunakan sintaks:
pointer_struct->elemen_2
Hal yang lain yang perlu anda perhatikan adalah, anda tidak dapat langsung menggunakan
sebuah struct tanpa membuat objek dari struct tersebut, sebab struct nama_struct
juga adalah tipe data seperti halnya int, bukan merupakan variabel, jadi seperti
pada int, anda harus membuat variabel dulu sebelum menggunakannya.
Selanjutnya kita akan membahas sebuah program yang memanfaatkan struct. Berikut ini source code program tersebut:
12 typedef dapat digunakan untuk mengganti makro#define
untuk mempersingkat penulisan tipe data, misalnya, sintaks yang ekivalen dengan
#define UINT unsigned int
adalah:
typedef unsigned int UINT ;
#include < stdio.h >
#include < malloc.h >
#include < string.h >
typedef unsigned int UINT;
typedef struct Microprocessor{
UINT kecepatan;
UINT harga;
char nama[20];} uP, *puP; /* deklarasi struktur */
void harga_per_MHz(puP pProc); /* deklarasi fungsi */
void main()
{
puP Duron = malloc(sizeof(uP));/* alokasikan memori */
uP Celeron;
Duron->harga = 350000;
Duron->kecepatan = 1100;
strcpy(Duron->nama, "Duron");
Celeron.harga = 700000;
Celeron.kecepatan = 2000;
strcpy(Celeron.nama, "Celeron");
harga_per_MHz(Duron);
harga_per_MHz(&Celeron);
free(Duron);/* bebaskan memori yang dialokasikan */
}
void harga_per_MHz(puP pProc) /* definisi fungsi */
{
UINT harga_MHz = (pProc->harga)/(pProc->kecepatan) ;
printf("harga per MHz dari %s adalah %d \n",
pProc->nama, harga_MHz);
}
Output dari program di atas adalah:
harga per MHz dari Duron adalah 318
harga per MHz dari Celeron adalah 350
Pada source code di atas ada dua file header yang baru kita gunakan, yaitu malloc.h dan string.h. File malloc.h digunakan untuk menyediakan kemampuan bagi program kita untuk mengalokasikan memori secara dinamis menggunakan fungsi malloc() dan free. Fungsi
malloc(size_t size)
adalah fungsi untuk mengalokasikan
memori sebesar size
byte. Alokasi memori dinamis ini diperlukan karena
puP Duron
adalah sebuah pointer, dan seperti yang dijelaskan sebelumya,
setiap pointer harus diinisialisasi untuk menunjuk alamat memori tertentu atau
diinisialisasi dengan 0 (dideklarasikan dengan nilai nol) agar pointer tersebut
tidak menjadi stray pointer, yang dapat menimbulkan bug. Memori yang dialokasikan
secara dinamis harus "dibebaskan" setelah digunakan dan tidak diperlukan
lagi dengan fungsi free()
, jika tidak maka memori tersebut tidak
akan dapat dipakai lagi selama program kita masih berjalan, hal ini lah yang disebut
memory leak. Memory leak adalah salah satu bug yang paling berbahaya, apalagi
jika bug ini terjadi pada blok pernyataan yang mengalami perulangan, karena akan
membuat sistem kita kehilangan "free memory" (memory yang dapat dipakai
oleh aplikasi lain) dalam jumlah besar, dan bisa menimbulkan hang. Jadi berhati-
hatilah saat menggunakan alokasi memori dinamis, pastikan bahwa setiap fungsi
malloc(size_t size)
diimbangi oleh fungsi free()
. File
string.h digunakan untuk menyediakan kemampuan memindahkan string (kelompok karakter)
dari satu variabel ke variabel lain atau dari sebuah string ke variabel yang dapat
menyimpan string. Hal ini dilakukan dengan fungsi strcpy()
. Pada
sourcecode di atas kita memindahkan string ke elemen nama
dari variabel
bertipe struct Microprocessor
yang kita buat.Sekarang kita akan membahas source code di atas dengan lebih detail. Pada bagian awal program, kita mendeklarasikan sebuah struct dengan nama
Microprocessor
,
struct ini mengandung 3 elemen, yaitu kecepatan, harga dan nama. Pada deklarasi
struct itu juga kita memberi 2 nama alias untuk struct ini, yaitu uP yang merupakan
nama alias "biasa", dan puP yang merupakan pointer ke struct dengan
tipe Microprocessor. Setelah itu dideklarasikan sebuah fungsi tanpa return value
yang mempunyai satu parameter dengan tipe pointer ke struct Microprocessor (fungsi
harga_per_MHz), fungsi ini menggunakan passing by pointer seperti yang sudah kita
bahas sebelumnya. Pada definisi fungsi ini tampak bahwa fungsi ini akan menghitung
harga per-MHz untuk setiap variabel (bertipe struct Microprocessor) melalui pointer
ke variabel tersebut yang menjadi parameter inputnya. Hasil perhitungan ini kemudian
di tampilkan ke user. Pada fungsi main()
, kita mendeklarasikan 2
buah variabel bertipe struct Microprocessor, 1 secara langsung, yaitu Celeron
,
dan satu secara tidak langsung, yaitu Duron
. Kemudian kedua variabel
ini diisi seluruh elemennya dengan nilai yang kita inginkan dengan menggunakan
operator yang sesuai, setelah itu fungsi harga_per_MHz(...)
dipanggil
untuk menampilkan harga per MHz dari setiap variabel ini. Fungsi harga_per_MHz
menggunakan parameter bertipe pointer ke struct, itulah sebabnya saat kita memanggil
fungsi ini pada variabel Celeron
, harus digunakan & (address
of operator) agar kita memberikan masukan yang tepat ke fungsi tersebut, yaitu
pointer ke struct yang kita miliki. Address of operator akan memberikan alamat
dari suatu variabel, hal ini sama saja artinya dengan memberikan pointer ke variabel
yang kita inginkan. Pembahasan mengenai struct hanya sampai di sini saja, untuk
mengetahui lebih lanjut anda dapat membaca referensi yang ada pada akhir tulisan
ini. Sekarang kita akan membahas sebuah bentuk yang dinamakan union. Union pada dasarnya mirip dengan struct, bahkan sintaks untuk deklarasinya sama saja. Yang membedakan struct dan union adalah cara kerjanya. Dalam sebuah struct, seluruh elemen struct tersebut berisi sesuatu pada suatu saat, sedangkan dalam sebuah union, pada satu saat hanya ada ada 1 elemen yang dapat digunakan. Dalam sebuah union biasanya elemennya mempunyai tipe data yang berbeda-beda. Bagi anda yang pernah menggunakan bahasa pemrograman Visual Basic, union akan tampak analog dengan variant. Union mempunyai cara kerja yang mirip "variabel template", karena kita dapat memasukkan variabel dengan tipe data yang telah kita definisikan sebagai elemen union tersebut ke dalam union tersebut pada saat run time (saat program sedang berjalan) dan pada satu saat hanya satu elemen saja yang dapat digunakan. Di bawah ini adalah sintaks untuk mendeklarasikan union :
union nama_union{
tipe_data elemen_1;
tipe_data elemen_2;
...
tipe_data elemen_n;}variabel_union1,variabel_union2,..
;
atau dengan menggunakan keyword typedef:
typedef union nama_union{
tipe_data
elemen_1;
tipe_data
elemen_2;
...
tipe_data
elemen_n;}nama_alias, *nama_alias_ponter;
Selanjutnya kita akan membahas sebuah penggunaan union. Berikut ini adalah sebuah contoh implementasinya.
Catatan:
nama_union maupun nama_struct pada deklarasi sebuah struct atau union adalah opsional, anda dapat menggunakan sebuah nama untuk sebuah struktur dan dapat mengabaikannya, tergantung dari kemauan anda.
#include < stdio.h >
#define CHARACTER 'C'
#define INTEGER 'I'
#define FLOAT 'F'
typedef struct data_holder{
char type;
union shared_tag {
char c;
int i;
float f;
} shared;
}generic_tag;
void print_function( generic_tag generic );
void main()
{
generic_tag var;
var.type = CHARACTER;
var.shared.c = '$';
print_function( var );
var.type = FLOAT;
var.shared.f = (float) 12345.67890;
print_function( var );
var.type = 'x';
var.shared.i = 111;
print_function( var );
}
void print_function( generic_tag generic )
{
printf("\n\nThe generic value is...");
switch( generic.type )
{
case CHARACTER: printf("%c", generic.shared.c);
break;
case INTEGER: printf("%d", generic.shared.i);
break;
case FLOAT: printf("%f", generic.shared.f);
break;
default: printf("an unknown type: %c\n",
generic.type);
break;
}
}
Output dari program di atas adalah: The generic value is...$
The generic value is...12345.678711
The generic value is...an unknown type: x
Contoh di atas adalah contoh penggunaan union yang umum, yaitu sebagai variabel template. Pada contoh di atas, dideklarasikan sebuah struct dengan nama data_holder dan nama alias generic_tag yang salah satu komponennya adalah union dengan nama shared_tag, union ini dapat menyimpan data dengan tipe char, float atau integer (int). Didefinisikan beberapa konstanta yang digunakan sebagai "pengenal" untuk data yang dimasukkan ke union ini, yaitu CHARACTER, FLOAT, INTEGER. Pada fungsi
main()
, didefinisikan sebuah variabel struct data_holder dengan
nama var. Elemen type dari struct ini kemudian isinya diubah-ubah pada saat yang
bersamaan, tergantung data yang dimasukkan ke elemen shared yang merupakan sebuah
variabel union. Setelah itu, isi dari union yang ada pada var ditampilkan kepada
user dengan menggunakan fungsi print_function. Fungsi ini bekerja dengan cara
mengecek tipe data yang telah dimasukkan ke var melalui elemen type yang dimiliki
var saat ini. Pengecekan ini dilakukan melalui sebuah peryataan menggunakan switch.
Jika anda masih kesulitan memahami penggunaan union, hal itu tidak terlalu mengganggu,
sebab feature ini jarang digunakan, tujuan penulis hanya ingin memperkenalkan
union kepada anda :). Lebih Lanjut Tentang Fungsi
Pada bagian ini kita akan membahas penggunaan fungsi dengan memanfaatkan pointer.
Nama sebuah fungsi pada dasarnya adalah pointer ke alamat fungsi tersebut. Untuk
mempermudah pemahaman konsep ini, marilah kita coba implementasi berikut:
#include < stdio.h >
#define FALSE 0
#define TRUE 1
void tukar(int * pX, int * pY);
void kuadrat(int * pX, int * pY);
void reset(int * pX, int * pY);
void main()
{
int keluar = FALSE;
int inrange = TRUE;
int var1;
int var2;
int pilihan;
void (*ptr_func) (int * , int *);
while (keluar != TRUE)
{
if (keluar == TRUE)
break;
printf("variabel pertama : ");
scanf("%d", &var1); printf("variabel pertama
: ");
scanf("%d", &var2); printf("Ketikkan fungsi
yang anda pilih (1-3) ");
scanf("%d", &pilihan); switch(pilihan)
{
case 1:
ptr_func = tukar ;
inrange = TRUE;
break;
case 2:
ptr_func = kuadrat;
inrange = TRUE;
break;
case 3:
ptr_func = reset;
inrange = TRUE;
break; default:
printf("Fungsi yang anda pilih tidak didefinisikan\n");
inrange = FALSE;
break;
}
if(inrange)
{
printf("Variabel 1 dan 2 sebelum fungsi di panggil: \n");
printf("Variabel 1: %d ; variabel 2: %d \n\n", var1, var2);
ptr_func(&var1, &var2); printf("Variabel 1 dan 2
setelah fungsi di panggil: \n");
printf("Variabel 1: %d ; variabel 2: %d \n\n", var1, var2);
}
printf("Apakah anda ingin mengakhiri program (1 = ya, 0 = tidak) ");
scanf("%d",&keluar);
}
}
void tukar(int * pX, int * pY)
{
int temp;
printf("tukar function called \n");
printf("Menukar variabel 1 dan variabel 2\n");
temp = *pX;
*pX = *pY;
*pY = temp;
}
void kuadrat(int * pX, int * pY)
{
printf("kuadrat function called \n"); *pX = (*pX)*(*pX);
*pY = (*pY)*(*pY);
}
void reset(int * pX, int * pY)
{
printf("reset function called \n");
*pX = 0;
*pY = 0;
}
Contoh output program di atas adalah:
variabel pertama : 3
variabel pertama : 4
Ketikkan fungsi yang anda pilih (1-3) 1
Variabel 1 dan 2 sebelum fungsi di panggil:
Variabel 1: 3 ; variabel 2: 4tukar function called
Menukar variabel 1 dan variabel 2
Variabel 1 dan 2 setelah fungsi di panggil:
Variabel 1: 4 ; variabel 2: 3Apakah anda ingin mengakhiri program
(1 = ya, 0 = tidak) 1
Contoh di atas mengeksploitasi kemampuan bahasa C menggunakan pointer fungsi. Pointer fungsi seperti yang telah dijelaskan diatas, adalah pointer juga, tetapi alamat yang ditunjuk oleh pointer tersebut bukan alamat variabel, tetapi alamat sebuah fungsi. Format deklarasi sebuah pointer fungsi adalah:
Peringatan !
Berhati-hatilah saat menjalankan program di atas, sebab program ini tidak bug free, masukkan input sesuai dengan rentang nilai yang diminta. Jika anda menyalahi aturan ini maka bisa saja muncul bug yang akan membuat komputer anda hang.
return_type (*pointer_fungsi) (tipe_data1 , tipe_data2 );
return_type
adalah tipe data dari return value (nilai yang dikembalikan/
dihasilkan sebuah fungsi, anda masih ingat kan ?), pointer_fungsi
adalah nama dari pointer fungsi yang diinginkan, tipe_data1
adalah
tipe data dari parameter pertama fungsi yang ditunjuk oleh pointer fungsi yang
kita deklarasikan, tipe_data2
adalah tipe data parameter kedua dari
fungsi yang ditunjuk oleh pointer fungsi yang kita deklarasikan. Jumlah tipe data
parameter yang digunakan bisa saja 0 atau bukan 2, tergantung dari format fungsi
yang ditunjuk oleh pointer fungsi yang kita buat. Pointer fungsi dapat menunjuk
ke berbagai macam fungsi, asalkan fungsi-fungsi tersebut mempunyai return type
dan tipe data parameter yang sama persis, seperti yang diperlihatkan pada contoh
di atas, pointer ke fungsi yang diberi nama ptr_func
menunjuk ke
tiga macam fungsi yang berbeda, yaitu fungsi tukar, kuadrat dan reset, tergantung
pilihan yang diketikkan user. Mungkin anda bertanya-tanya mengapa begitu banyak
tanda kurung, hal ini dibutuhkan, sebab jika anda menuliskannya seperti inireturn_type * pointer_fungsi (tipe_data1 , tipe_data2 );
maka and mendeklarasikan sebuah fungsi dengan return value bertipe pointer ke
return_type
, bukan pointer fungsi seperti yang kita inginkan. Anda
juga mungkin bertanya, mengapa kita tidak perlu menggunakan indirection operator
(*) untuk memanggil fungsi yang kita inginkan dari pointer-nya. Anda bisa saj
melakukan- nya jika anda menginginkannya, seperti ini:(*ptr_func)(&var1,&var2);
namun hal ini tidak ada bedanya, sebab keduanya diperlakukan sama saja oleh bagi compiler C. Contoh di atas mungkin cukup sulit dipahami, anda perlu melihat kembali bagian pointer jika masih bingung dengan sintaks yang digunakan. Sintaks
*pX =
(*pX)*(*pX);
maksudnya adalah kalikan nilai yang ditunjuk
oleh pointer pX dengan nilainya sendiri, kemudian kopi nilai tersebut ke variabel
itu sendiri (variabel yang ditunjuk oleh pX). Secara garis besar, program di atas mula-mula mengeset variabel keluar menjadi
FALSE
sehingga saat perulangan while(keluar != TRUE)
mulai dieksekusi, program akan berulang-ulang menampilkan tampilan yang ada di
dalam while, sampai user memilih untuk keluar (keluar == TRUE
) ,
pada saat itu, perintah break yang ada di dalam looping while (di bawah
if (keluar == TRUE)
) akan dieksekusi, sehingga program keluar dari looping
tersebut. Di dalam looping inilah terdapat penggunaan pointer fungsi.
Selanjutnya kita akan membahas sebuah program yang menggunakan pointer ke fungsi yang mirip dengan yang banyak digunakan dalam pemrograman windows.
Obrolan bebas:
"Sampai saat ini kita telah mempelajari banyak konsep yang sangat penting, mulai dari variabel 'biasa' sampai pointer fungsi, dan penulis yakin pasti cukup melelahkan dan membingungkan :( . Tapi anda tidak perlu merasa tertekan , karena konsep itu semua digunakan jika anda berniat menjadi programer yang hebat. Setelah memahami tutorial ini, mudah-mudahan anda tidak bermasalah lagi dengan apa yang disebut pemrograman C. Setelah membaca tutorial assembly nanti, pemahaman anda tentang bahasa C akan menjadi setingkat lebih tinggi sebab banyak hal, seperti pointer fungsi yang baru saja di bahas sebenarnya datang dari penggunaannya dalam pemrograman assembly. Mungkin anda juga penasaran, mengapa tutorial ini belum juga masuk ke pemrograman windows. Anda perlu bersabar sebelum sampai ke sana, sebab hal itu adalah salah satu hal yang penulis sendiri rasakan sangat sulit dipahami jika konsep-konsep seperti yang dijelaskan di sini tidak dikuasai dengan baik. penulis sendiri butuh waktu 1 bulan untuk benar-benar memahami apa yang dilakukan setiap baris dari pemrograman dengan bahasa C pada windows dengan menggunakan windows API :( . So keep fighting till the end"
#include < stdio.h >
typedef unsigned int UINT;
typedef UINT (*FPTR) (UINT var1, UINT var2);
typedef struct Input { FPTR pFunc;
UINT param1;
UINT param2;} INPUT ;
UINT kali(UINT faktor1, UINT faktor2)
{
return (faktor1 * faktor2);
}
void Hitung(INPUT input)
{
int hasil = input.pFunc(input.param1, input.param2);
printf("Hasil perhitungan adalah: %d \n", hasil);
}
void main()
{
INPUT in;
in.pFunc = kali;
in.param1 = 10;
in.param2 = 20; Hitung(in);
}
Output dari program di atas adalah:
Hasil perhitungan adalah: 200
Program di atas menggunakan beberapa sintaks yang "baru" misalnya
sintaks untuk mempersingkat penulisan pointer fungsi, yaitu:
typedef UINT (*FPTR) (UINT var1, UINT var2);
sintaks ini maksudnya adalah, FPTR adalah sebuah sintaks untuk menyatakan pointer
fungsi yang return value-nya bertipe UINT dan memiliki dua parameter bertipe
UINT. Selanjutnya dideklarasikan sebuah struct dengan nama Input
dengan
elemen-elemen:
- Sebuah variabel bertipe pointer ke fungsi (FPTR)
- Dua buah variabel bertipe UINT (unsigned int)
Kali
dan Hitung
.
Fungsi Kali
bekerja dengan mengalikan kedua parameter inputnya. Fungsi
Hitung
adalah sebuah fungsi yang menerima input berupa objek bertipe
struct Input
, mengalikan kedua elemen struct tersebut yang bertipe
UINT dan kemudian menampilkan hasilnya. Pada fungsi main()
, dibuat
sebuah objek bertipe struct Input,
kemudian objek tersebut dijadikan
parameter untuk memanggil fungsi Hitung. Sampai di sini anda sudah memiliki sebagian
besar skill yang dibutuhkan untuk melakukan pemrograman C pada Windows dengan
menggunakan Windows API (Windows Application Programming Interface).
Arsitektur Driver Windows 2000/XP
Penulis mohon maaf karena semestinya ada bagian pendahuluan yang menerangkan
tentang Windows API (Application Programming Interface) sebelum masuk kepada
bagian ini, tetapi akibat keterbatasan waktu maka bagian tersebut belum sempat
disertakan. Anda tidak perlu khawatir sebab hal tersebut tidak akan mengurangi
pemahaman anda terhadap source code driver yang dibahas. Dalam beberapa saat
ke depan mudah-mudahan bagian tersebut dapat disertakan dalam tulisan ini setelah
tulisan ini diupdate :). Berikut ini kita akan membahas source code driver pada
artikel Tutorial Membuat Patch dalam bentuk Driver Pada Windows 2000/XP.
/*
patch.c -- prototipe Patch
project started 9 Feb 2003 by - Pinczakko -
*/
#include "ntddk.h"
#define FILE_DEVICE_UNKNOWN 0x00000022
#define PCI_ADDR_PORT 0xCF8
#define PCI_DATA_PORT 0xCFC
#define REG_ADDR 0x80000050
#define MASK 0x00000040
void tUnloadDriver(PDRIVER_OBJECT DriverObject);
NTSTATUS tDispatchCreate(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp);
NTSTATUS tDispatchClose(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp);
void PatchPCI(ULONG reg_addr, ULONG mask);
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject,
IN PUNICODE_STRING RegistryPath)
/*++
Routine Description:
This routine is called when the driver is loaded by NT.
Arguments:
DriverObject - Pointer to driver object created by system.
RegistryPath - Pointer to the name of the services node for this driver.
Return Value:
The function value is the final status from the initialization operation.
--*/
{
NTSTATUS ntStatus;
UNICODE_STRING uszDriverString;
UNICODE_STRING uszDeviceString;
PDEVICE_OBJECT pDeviceObject;
// Point uszDriverString at the driver name
RtlInitUnicodeString(&uszDriverString, L"\\Device\\Patch");
// Create and initialize device object
ntStatus = IoCreateDevice(DriverObject,
0,
&uszDriverString,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&pDeviceObject);
if(ntStatus != STATUS_SUCCESS)
return ntStatus;
// Point uszDeviceString at the device name
RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\Patch");
// Create symbolic link to the user-visible name
ntStatus = IoCreateSymbolicLink(&uszDeviceString, &uszDriverString);
if(ntStatus != STATUS_SUCCESS)
{
// Delete device object if not successful
IoDeleteDevice(pDeviceObject);
return ntStatus;
}
//Patch the chipset on driver loading stage
PatchPCI(REG_ADDR ,MASK);
// Load structure to point to IRP handlers...
DriverObject->DriverUnload = tUnloadDriver;
DriverObject->MajorFunction[IRP_MJ_CREATE] = tDispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = tDispatchClose;
// Return success
return ntStatus;
}
NTSTATUS tDispatchCreate(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return(STATUS_SUCCESS);
}
NTSTATUS tDispatchClose(IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp)
{
Irp->IoStatus.Status = STATUS_SUCCESS;
Irp->IoStatus.Information=0;
IoCompleteRequest(Irp, IO_NO_INCREMENT);
return(STATUS_SUCCESS);
}
void tUnloadDriver(PDRIVER_OBJECT DriverObject)
{
UNICODE_STRING uszDeviceString;
IoDeleteDevice(DriverObject->DeviceObject);
RtlInitUnicodeString(&uszDeviceString, L"\\DosDevices\\Patch");
IoDeleteSymbolicLink(&uszDeviceString);
}
void PatchPCI(ULONG reg_addr, ULONG mask)
{
//Patch the chipset
__asm
{
pushfd ;//save all flags and regs
pushad
mov eax,reg_addr ;//fetch the address of the regs to be patched
mov dx,PCI_ADDR_PORT ;//fetch the input port addr of PCI cfg space
out dx,eax
mov dx,PCI_DATA_PORT
in eax,dx
or eax,mask ;//mask the regs value (activate certn. bits)
out dx,eax
popad ;//restore all flags
popfd
}
}
Warning !
Jangan mencoba menginstalasi driver yang dihasilkan oleh source code di atas pada sistem anda, karena dapat membuat sistem anda rusak.
Pada program di atas, entry point program ada pada fungsi DriverEntry , fungsi
ini dipanggil oleh Windows saat driver yang dihasilkan dari source code ini
di-load oleh windows saat start-up. Windows memanggil fungsi ini dengan menyertakan
beberapa parameter, yaitu: sebuah struct dengan tipe PDRIVER_OBJECT dan sebuah
struct dengan tipe PUNICODE_STRING. Keyword IN sebenarnya diabaikan oleh compiler
saat source code ini di-compile. Keyword tersebut hanya digunakan untuk memperjelas
saat kita membaca source code. Kita akan membahas Source code ini mulai dari
bagian awal. Pada awal source code terdapat beberapa definisi konstanta, tentang
arti definisi ini telah dijelaskan pada artikel Tutorial Membuat Patch dalam
bentuk Driver Pada Windows 2000/XP. Namun, konstanta: FILE_DEVICE_UNKNOWN perlu
dijelaskan di sini. Konstanta ini digunakan sebagai pengenal bagi driver yang
kita buat, kita menggunakan FILE_DEVICE_UNKNOWN karena driver kita bukan merupakan
driver spesifik untuk device tertentu yang telah dikenali secara internal oleh
Windows, misalnya Hard drive atau mouse. Selanjutnya 3 buah fungsi minimum yang
harus dimiliki oleh sebuah driver, nama dari fungsi-fungsi ini tidak harus seperti
yang diperlihatkan pada source code di atas, sebab ketiga fungsi ini ditunjuk
oleh pointer yang ada dalam fungsi DriverEntry , jadi jika anda merubah namanya,
anda hanya perlu merubah nama pointer fungsi yang ada pada fungsi DriverEntry.
Selanjutnya kita melangkah ke inti dari program ini yaitu fungsi DriverEntry.
Fungsi ini sebenarnya adalah fungsi inisialisasi driver. Inisialisasi tersebut
kita lakukan dengan cara melakukan apa yang kita inginkan pada fungsi ini, saat
membuat patch, kita ingin mem-patch beberapa register, dan hal itulah yang kita
lakukan pada source code ini dengan memanggil fungsi PatchPCI. Pada awal fungsi
ini didefinisikan beberapa variabel, yaitu:
NTSTATUS ntStatus;
UNICODE_STRING uszDriverString;
UNICODE_STRING uszDeviceString;
PDEVICE_OBJECT pDeviceObject;
NTSTATUS adalah sebuah tipe data yang digunakan secara internal oleh Windows
untuk menangani fungsi-fungsi yang berkaitan dengan driver, tipe data ini adalah
tipe data 32 bit. Variabel ntStatus adalah variabel yang digunakan untuk mengecek
apakah proses loading (inisialisasi) driver ini berhasil, jika berhasil maka
variabel ini akan bernilai STATUS_SUCCESS setelah fungsi IoCreateDevice dipanggil.
Sedangkan kedua variabel bertipe UNICODE_STRING digunakan untuk mengatur nama
driver ini yang akan muncul di registry saat driver ini telah berhasil di-load.
Variabel pDeviceObject adalah variabel yang digunakan untuk menyimpan pointer
ke struct bertipe DEVICE_OBJECT yang dihasilkan setelah kita berhasil me-load
driver kita (ntStatus bernilai STATUS_SUCCESS), atau dengan kata lain pointer
ke driver kita.
Fungsi IoCreateDevice adalah fungsi inti yang menginisialisasi driver ini. Fungsi
ini digunakan untuk "membuat driver", cara kerjanya adalah sebagai
berikut:
- Parameter pertama dari fungsi ini adalah "template driver" yang
disediakan oleh windows bagi driver yang akan "dibuat". "template
driver" ini disediakan windows dalam bentuk parameter pertama bagi fungsi
DriverEntry yang kita buat (yang akan dipanggil oleh windows saat loading
driver). "template driver" ini sebenarnya sebuah struct yang isinya
pointer ke fungsi yang kita definisikan sendiri pada baris-baris program selanjutnya.
"template driver" inilah yang kita modifikasi pada baris :
DriverObject->DriverUnload = tUnloadDriver;
DriverObject->MajorFunction[IRP_MJ_CREATE] = tDispatchCreate;
DriverObject->MajorFunction[IRP_MJ_CLOSE] = tDispatchClose;
dengan source code ini, kita membuat "template driver" yang diberikan oleh windows menunjuk ke fungsi-fungsi yang kita inginkan. Fungsi tUnloadDriver dipanggil saat driver kita akan di-unload, saat windows akan shutdown, pada fungsi ini yang kita lakukan hanyalah cleanup memory dan resources yang digunakan oleh driver ini. Fungsi tDispatchCreate dipanggil pada saat ada software pada user mode yang membuka akses ke driver kita, karena kita tidak beururusan dengan software di user mode maka dalam driver ini, fungsi ini hanya mengembalikan nilai STATUS_SUCCESS. Fungsi tDispatchClose dipanggil pada saat ada software pada user mode yang menutup akses ke driver kita, karena kita tidak beururusan dengan software di user mode maka dalam driver ini, fungsi ini hanya mengembalikan nilai STATUS_SUCCESS
- Parameter kedua adalah jumlah memory yang dibutuhkan oleh driver, namun
kita tidak membutuhkan memory apapun untuk driver ini sehingga parameter tersebut
0.
- Parameter ke-3 adalah nama dari driver kita yang akan tampak oleh user
mode application. Dalam kasus ini kita tidak perlu membahasnya karena tidak
ada user mode application yang menggunakan driver kita.
- Parameter ke-4 adalah tipe device dari driver kita, karena driver ini tidak
memiliki device maka digunakan FILE_DEVICE_UNKNOWN. Jika device yang kita
buat adalah device yang dikenali windows, misalnya hard drive maka nilai variabel
ini akan berbeda. Untuk penjelasan lebih lanjut silahkan membaca file help
Windows 2000 Driver Development Kit.
- Parameter ke-5 adalah karakteristi device yang secara internal dikenali
oleh windows, tetapi device kita tidak dikenali, sehingga harus diberi nilai
0.
- Parameter ke-6 adalah parameter yang berkaitan dengan kemampuan driver
kita diakses dari usermode, jika dapat diakses oleh beberapa aplikasi pada
saat bersamaan maka nilainya FALSE. Sebenarnya dalam kasus seperti ini, nilai
parameter ini tidak punya pengaruh sebab kita tidak berurusan dengan software
di user mode.
- Parameter terakhir adalah pointer ke driver yang dihasilkan oleh fungsi
ini. Pointer ini adalah pointer ke struct DEVICE_OBJECT yang dihasilkan oleh
fungsi ini. Struct ini adalah struct yang menyimpan karakteristik driver kita.
Fungsi PatchPCI akan anda pahami setelah membaca bagian Tutorial Bahasa Assembly, jadi tidak akan dibahas di sini. Fungsi RtlInitUnicodeString digunakan untuk menyalin string, mirip fungsi strcpy pada pemrograman C yang telah kita pelajari. Dengan penjelasan ini semoga anda sudah memahami arsitektur driver windows 2000/XP secara umum.
Obrolan bebas:
"Selamat. Sekarang anda telah memiliki sebagian besar kemampuan dasar yang dibutuhkan untuk menjadi seorang programer bahasa C yang profesional, yang anda butuhkan adalah berlatih membuat beberapa program agar anda semakin memahami bagaimana bahasa ini digunakan :). Bagian selanjutnya akan mengajari anda cara memprogram dengan bahasa assembly. Ada mitos yang mengatakan bahwa programming menggunakan asssembly sangat susah. Menurut penulis, mitos tersebut adalah keliru. Jika anda mau berusaha dan membuang jauh-jauh mitos itu, maka belajar assembly akan lebih mudah dibandingkan belajar bahasa C, sebab sintaks yang harus kita pelajari lebih sedikit. Satu-satunya hal yang akan menjadi tantangan adalah anda harus berusaha memahami arsitektur microprocessornya. Jadi... selamat berjuang and always have fun :)"
Pemrograman Bahasa Assembly
Pemrograman bahasa assembly sangat erat kaitannya dengan hardware yang digunakan, oleh karena itu tutorial ini diawali dengan penjelasan tentang arsitektur x86. Kita tidak akan mempelajari pemrograman assembly yang rumit, yang akan kita pelajari hanya bagaimana cara membuat file *.com atau yang biasa disebut file flat binary.
Arsitektur Microprocessor x86
Pada bagian ini kita akan membedah bagian-bagian Microprocessor x86 secara fungsional dan mempelajari bagaimana bagian-bagian tersebut bekerja.
Arsitektur Dasar
Dalam sebuah arsitektur komputer ada filosofi tertentu yang digunakan, misalnya
dalam hal menentukan apakah kita menggunakan bus yang sama untuk data dan kode
program atau keduanya terpisah, dan lain-lain. Secara umum saat ini arsitektur
komputer yang ada menggunakan salah satu dari dua "aliran" arsitektur
komputer, yaitu Von Neumann Architecture (VNA) dan Harvard Architecture. Pada
VNA, bus untuk data dan program adalah sama, sedangkan pada Harvard Architecture,
bus untuk data dan program adalah berbeda. Keluarga x86 menggunakan menggunakan
VNA yang secara skematik digambarkan sbb:
CPU pada gambar di atas adalah microprocessor yang kita gunakan, memory adalah
RAM , dan I/O (Input/Output)devices adalah periferal lainnya, misalnya Video
Card, Chipset dan lain-lain. Garis merah tebal pada gambar di atas menunjukkan
system bus dari x86, sistem bus ini terbagi menjadi tiga komponen, yaitu data
bus, address bus dan control bus. Dari gambar di atas dapat anda lihat bahwa
periferal I/O termasuk chipset yang ada di motherboard kita menggunakan bus
yang sama dengan bus memory untuk berhubungan dengan microprocessor (system
bus). Teknik seperti ini biasa disebut memory mapped I/O, sebab periferal I/O
diakses seperti hal-nya kita mengakses memory. Barangkali anda bertanya-tanya,
mengapa northbridge 'hilang' dari skema di atas, northbridge sebenarnya ada
pada pertemuan garis merah tebal dari memory dan I/O devices , chip ini tidak
digambarkan sebab chip tersebut "transparan" terhadap program yang
kita buat. Maksudnya, chip tersebut tidak akan "mengubah" data apapun
yang kita lewatkan melaluinya. Di bawah ini adalah yang skema yang lebih jelas
tentang hubungan antara system bus dan microprocessor.
Pada skema di atas anda dapat melihat bahwa system bus terbagi menjadi tiga
bagian, yaitu data bus yang digunakan untuk mengirim data dari/ke memory maupun
I/O devices, address bus yang digunakan untuk menunjukkan alamat memory atau
alamat I/O yang dituju/asal data yang dikirimkan/diterima tersebut tersebut,
dan control bus yang digunakan untuk mengatur "status" dari data bus
dan address bus, misalnya apakah saat ini kita sedang menulis data ke RAM atau
ke I/O devices, apakah kita sedang membaca data dari RAM atau dari I/O devices,
dan lain-lain. BIU pada skema di atas adalah Bus Interface Unit, yang mengatur
komunikasi microprocessor dengan system bus. ALU adalah Arithmetic and Logic
Unit yang berfungsi sebagai komponen untuk melakukan operasi aritmetika dan
logika. Registers adalah register-register dari x86, yaitu tempat penyimpanan
data sementara (semacam memori) dengan kecepatan sangat tinggi. Register inilah
yang akan digunakan dalam pengolahan data di dalam microprocessor. Control Unit
adalah komponen yang bertugas mengatur lalu lintas data antara ALU, BIU dan
register-register yang ada di dalam microprocessor.
Data di dalam sebuah x86 dapat direpresentasikan dalam bentuk byte (8 bit),
word (16 bit) atau dword/double word (32 bit). Ketika mengeksekusi sebuah program,
yang terjadi sebenarnya adalah pemindahan data dari memori utama (RAM) ke microprocessor
x86, kemudian data ini diolah pada microprocessor dan kemudian di pindahkan
ke bagian output yang kita inginkan, misalnya ke ram dari video card atau memori
utama, dan lain-lain. Dalam bahasa assembly, kita akan lebih banyak berurusan
dengan pengolahan data di dalam microprocessor. Pengolahan data ini melibatkan
bagian microprocessor yang disebut register. Register sebenarnya merupakan tempat
penyimpanan dan pengolahan data yang ukurannya sangat kecil, tetapi kecepatan
akses-nya sangat tinggi. Operasi-operasi yang terjadi terhadap data yang ada
pada register-register ini terjadi pada ALU kemudian hasilnya di tempatkan ke
register, alamat memori atau alamat I/O, tergantung instruksi yang kita berikan.
Jika data yang diolah bertipe "floating point", maka pengolahannya
dapat menggunakan FPU (Floating Point Unit), namun hal ini juga tergantung instruksi
bahasa assembly yang kita gunakan. Berikut ini adalah skema dari "isi"
sebuah microprocessor x86 generasi ke 6 (i686 yaitu PentiumPro, Pentium II,
Celeron, Pentium III, Celeron II):
Pada gambar di atas, bagian kiri yang diberi nama ... register adalah isi dari
microprosesor itu sendiri dan yang di sebelah kanan, yang diberi label address
space adalah memori utama (RAM). Antara microprocessor dan memory utama (RAM)
sebenarnya ada yang disebut Bus Interface Unit yang menghubungkan microprocessor
dengan bus memory pada Northbridge dan ada pula Northbridge dari motherboard,
namun kedua komponen tersebut "transparan" terhadap program assembly
yang kita buat, maksudnya, instruksi yang kita buat akan dieksekusi sesuai dengan
alamat-alamat yang kita "minta", jadi kita tidak perlu mengubah-ubah
setting dari kedua komponen itu. Gambar di atas menampilkan arsitektur dari
x86 secara fungsional. Agar lebih jelas, maka register-register yang ada di
bagian kiri gambar di atas di tampilkan lebih rinci :
Dalam pemrograman assembly tingkat awal, kita hanya akan berurusan dengan register-register
yang ada di sebelah kiri, sedangkan memori utama yang ada di sebelah kanan tidak
terlalu berperan. Tutorial ini hanya akan berurusan dengan General Purpose
Register (GPR), tidak akan berurusan dengan Segment Register, sebab hal
tersebut telah diatur oleh Software Development Tool yang kita gunakan (Visual
C++, Masm atau Nasm). Namun demikian akan tetap ada sedikit penjelasan tentang
segment register. Di bawah ini adalah register-register yang termasuk GPR:
Register-register di atas mempunyai fungsi khusus yang agak berbeda. Berikut
ini ringkasannya.
- Register EAX , berfungsi sebagai "akumulator" yaitu register
utama yang digunakan untuk menyimpan data sementara yang akan diolah pada
ALU atau FPU, register ini juga digunakan sebagai tempat penyimpanan hasil
pengolahan data tersebut. EAX dapat mengakses semua register lainnya dan merupakan
register dengan akses tercepat, jadi sebisa mungkin kita meletakkan data pada
register ini.
- Register EBX, berfungsi sebagai pointer ke data di memory yang berada pada
segmen yang sedang ditunjuk oleh register DS.
- Register ECX, berfungsi sebagai "counter" (bilangan yang nilainya
berubah saat sebuah operasi telah dilakukan) dalam operasi menggunakan string
atau dalam perulangan.
- Register EDX, berfungsi sebagai register untuk melakukan komunikasi data
dengan alamat I/O.
- Register ESI, berfungsi sebagai pointer ke data pada segmen yang sedang
ditunjuk oleh register DS. Juga berfungsi sebagai pointer alamat sumber string
dalam operasi string.
- Register EDI, berfungsi sebagai pointer ke data pada segmen yang sedang
ditunjuk oleh register ES. Juga berfungsi sebagai pointer ke alamat tujuan
dalam operasi string.
- Register ESP, berfungsi sebagai pointer ke stack (pada segmen yang sedang
ditunjuk oleh register SS).
- Register EBP, berfungsi sebagai pointer ke data yang ada pada stack(pada
segmen yang sedang ditunjuk oleh register SS).
- Register SS (Stack Segment), berfungsi sebagai register yang menyimpan
alamat segment stack saat ini. Stack adalah bagian memori utama tempat penyimpanan
data sementara jika sebuah fungsi dieksekusi atau sebuah interupsi sedang
berlangsung atau pada saat sebuah prosedur dipanggil atau pada keadaan lain
saat sebuah program dengan sengaja menyimpan datanya di stack. Biasanya stack
berada pada bagian akhir memory (alamat terbesar) dan pointer ke stack (SP)
dikurangi setiap kali ada data baru yang di simpan di stack, sesuai dengan
ukuran data yang disimpan
Register CS (Code Segment), berfungsi menyimpan alamat segmen tempat kode program yang akan dieksekusi di simpan.
- Register DS (Data Segment), berfungsi menyimpan alamat segmen tempat data program yang akan dieksekusi di simpan.
Selain fungsi khusus di atas, GPR juga dapat digunakan dalam operasi pengolahan
data biasa yang terjadi antara GPR tersebut dengan ALU. Pada tutorial ini, yang
akan digunakan secara intensif hanya 4 GPR yang pertama, yaitu EAX, EBX, ECX
dan EDX.
Mode Kerja Microprocessor x86
Microprocessor x86 sejak i386 mempunyai 3 macam mode operasi. Mode operasi
adalah kondisi operasi Microprocessor tersebut yang menentukan fasilitas apa
saja dari Microprocessor yang dapat diakses, misalnya instruksi apa saja yang
dapat digunakan, jumlah memory yang dapat diakses dan lain-lain. Mode operasi
tersebut adalah (>= i386
) :
- Real Address Mode atau Real Mode. Mode ini hanya digunakan pada saat komputer
sedang boot atau saat tidak ada sistem operasi pada komputer yang kita miliki
dan hanya bios yang dapat bekerja atau kita sedang menjalankan sistem operasi
yang hanya dapat berjalan pada real mode misalnya DOS. Pada mode, tidak semua
feature yang dimiliki Microprocessor x86 (
>=i386
) dapat digunakan. Pada mode ini akses ke hardware secara langsung bebas dilakukan, dan alamat memory yang menyimpan kode sistem operasi juga dapat ditulisi, sehingga sistem dapat dengan mudah crash. Pada mode ini, ukuran register yang digunakan secara default adalah 16 bit, misalnya AX, CX, BX , dan lain-lain.
- Protected Mode. Mode ini adalah mode yang digunakan saat komputer kita
berada dalam sistem operasi protected mode, misalnya windows atau Linux. Pada
mode ini seluruh feature x86 (
>=i386
) dapat digunakan, dan alamat kode program yang "berbahaya", misalnya sistem operasi tidak dapat diakses, selain itu akses langsung ke hardware tidak diperbolehkan kecuali program kita mendapat ijin dari sistem operasi, misalnya untuk program yang menggunakan device driver. Pada mode ini, ukuran register yang digunakan secara default adalah 32 bit, misalnya EAX, ECX, EBX , dan lain-lain - System Management Mode. Mode ini adalah mode power management untuk penghematan daya.
Programer assembly konvensional biasanya menjalankan programnya pada real mode.
Namun kita tidak perlu menjalankan komputer kita dengan sistem operasi real
mode seperti DOS untuk mencoba program yang seharusnya berjalan pada real mode,
sebab pada protected mode (misalnya saat anda sedang menjalankan windows 2000)
ada yang disebut virtual-8086 mode, sehingga program yang anda buat "seakan-akan"
berjalan pada real mode. Mode ini pada dasarnya adalah emulasi sehingga tidak
dijamin semua instruksi dapat dijalankan sesuai dengan yang kita inginkan. Virtual-8086
mode dapat digunakan dengan mudah, yaitu anda tinggal membuat source code assembly
kemudian buat executable dan jalankan pada console windows, program itu akan
otomatis berjalan pada mode ini jika memang program tersebut memanfaatkan instruksi-instruksi
real mode.
Arsitektur x86 Modern
Modern Microprocessor x86 modern seperti Athlon, Pentium III atau Pentium 4
memiliki arsitektur internal yang sedikit berbeda dengan apa yang dideskripsikan
di sini sebab microprocessor tersebut sudah begitu kompleks dengan jumlah register
yang jauh lebih banyak dan memanfaatkan apa yang disebut superscalar operation,
pada salah satu gambar di atas anda melihat ada yang di sebut execution unit,
Pentium III mempunyai 3 execution unit semacam ini, Athlon juga mempunyai 3
execution unit seperti ini, jadai anda dapat membayangkan, jika ada 3 instruksi
yang tidak saling berhubungan, maka ketiga instruksi tersebut dapat dieksekusi
hanya dalam 1 cycle (cycle adalah clock yang dibutuhkan untuk menyelesaikan
eksekusi 1 instruksi). Selain itu, pada Microprocessor modern terdapat floating
point unit yang mempunyai sangat banyak register, misalnya untuk SSE (Streaming
SIMD) terdapat 8 register floating point. Di luar itu semua masih banyak lagi
fasilitas yang sebenarnya ada di dalam microprocessor tersebut namun sangat
jarang atau bahkan tidak dimanfaatkan, misalnya: sejak microprocessor generasi
i686 (PentiumPro), kemampuan mengalamatkan memori yang ada sebenarnya sudah
mencapai 64 GB dengan menggunakan apa yang disebut Physical Address Extension
(PAE), bukan 4 GB seperti yang saat ini sedang banyak diributkan orang, hanya
saja sistem operasi yang benar-benar memberikan dukungan pada kemampuan ini
hanya Linux dan Windows 2000 edisi tertentu. Namun kita tidak perlu khawatir
bahwa program yang kita buat tidak akan berjalan pada "mesin-mesin"
tersebut, sebab semuanya kompatibel ke belakang dengan instruksi yang dibuat
untuk "mesin-mesin" generasi sebelumnya.
Sintaks Bahasa Assembly x86
Sekarang kita akan mempelajari sintaks yang digunakan dalam bahasa assembly
untuk x86. Tool yang anda butuhkan untuk mencoba contoh-contoh yang ada adalah
Nasm atau Microsoft Macro Assembler 6.11, cara menggunakan kedua tool ini akan
dijelaskan.
Setiap instruksi yang dapat diubah menjadi machine code oleh assembler disebut
sebagai instruction set. Setiap generasi microprocessor mempunyai instruction
set yang jumlahnya berbeda-beda, tetapi semuanya memiliki instruction set yang
bersifat umum yang dapat dieksekusi pada seluruh microprocessor tersebut. Kita
akan menggunakan instruction set yang dapat dieksekusi oleh microprocessor i386
ke atas. Sebelum mempelajari instruction set, terlebih dahulu kita akan mempelajari
cara membuat variabel dan konstanta dalam bahasa assembly. Sintaks untuk membuat
konstanta adalah:
nama_konstanta equ nilai_konstanta
sintaks ini analog dengan sintaks berikut dalam bahasa C:
#define nama_konstanta nilai_konstanta
contoh:
bank_mask equ 20000840h
akhiran h pada sintaks di atas adalah akhiran yang menunjukkan bahwa nilai tersebut
adalah nilai hexadecimal, jika tidak diberi akhiran maka nilai tersebut akan
dianggap desimal (basis 10). Anda perlu berhati-hati jika menyatakan konstanta
yang nilainya diawali dengan huruf dalam hexadecimal, misalnya: sebuah konstanta
bernilai CFCh, maka anda harus memberi awalan 0 saat mendeklarasikannya, jadi
hasilnya :
bank_mask equ 0CFCh
Sintaks di atas berlaku baik pada masm maupun nasm. Selanjutnya kita akan mempelajari
sintaks untuk membuat variabel, formatnya adalah:
nama_variabel tipe_data nilai_variabel
pada sintaks ini, nama_variabel adalah nama dari variabel yang diinginkan, tipe_data
adalah tipe data dari variabel tersebut, dalam bahasa asssembly x86 dikenal
beberapa tipe data yaitu:
word, tipe data ini dapat menyimpan data dengan ukuran 2 byte (16 bit). Sintaks yang digunakan untuk mendeklarasikan variabel dengan tipe ini adalah dw
dword, tipe data ini dapat menyimpan data dengan ukuran 4 byte (32 bit). Sintaks yang digunakan untuk mendeklarasikan variabel dengan tipe ini adalah dd
contoh:
test db 78h
akhiran h pada sintaks di atas adalah akhiran yang menunjukkan bahwa nilai tersebut adalah nilai hexadecimal. Sama seperti pada deklarasi konstanta, jika anda mendeklarasikan variabel dengan nilai hexadecimal, maka berikan awalan (prefix) 0 jika variabel tersebut nilai varaibel tersebut diawali huruf. Contoh:
test db 0FEh
Perlu anda ketahui bahwa kebanyakan assembler adalah tidak case sensitive.
Hal ini perlu anda perhatikan saat memberi nama variabel atau konstanta pada
program yang anda buat. Sintaks dasar sebuah pernyataan dalam bahasa assembly
adalah :
label: instruction operands ; comment
label pada sintaks di atas adalah opsional. Label biasanya digunakan pada awal
instruksi yang akan mengalami perulangan atau pada instruksi yang akan dipanggil
dalam sebuah jump. instruction adalah instruksi yang akan dieksekusi, operands
adalah variabel yang akan dimanipulasi, jumlah operand tergantung instruksi
yang kita gunakan, ada juga instruksi yang tidak mempunyai operand sama sekali.
comment adalah komentar, karakter yang ada di belakang tanda titik koma (;)
akan dianggap sebagai komentar sampai baris tersebut berakhir. Berikut ini beberapa
instruction set yang akan kita pelajari:
Instruction Set |
Kegunaan |
mov dest,src |
instruksi ini memindahkan isi dari src ke dest. src maupun dest dapat merupakan register atau alamat memory, tetapi salah satunya harus merupakan register, sebab tidak ada instruksi yang dapat langsung memindahkan isi sebuah alamat memory ke alamat memory lainnya.Instruksi ini juga dapat menggunakan operand 8, 16 atau 32 bit. |
pushfd |
instruksi ini digunakan untuk menyimpan isi register flag di stack. |
popfd |
instruksi ini digunakan untuk me-restore isi register flag yang tadinya di simpan di stack dengan instruksi pushfd. |
pushad |
menyimpan isi seluruh register GPR yang pada stack. |
popad |
me-restore isi seluruh register GPR yang tadinya disimpan di stack dengan instruksi pushad. |
je label |
instruksi ini digunakan untuk berpindah (dengan syarat) ke instruksi yang "sejajar" dengan label pada source code yang kita buat. Jika bit ZF (Zero Flag) pada flag register bernilai 1, maka program akan dilanjutkan ke label jika tidak maka eksekusi dilanjutkan ke instruksi selanjutnya setelah je. |
jmp label |
instruksi ini digunakan untuk berpindah (tanpa syarat) ke instruksi yang "sejajar" dengan label pada source code yang kita buat. |
inc var |
instruksi ini digunakan untuk menaikkan nilai yang ada pada register var satu satuan. var adalah sebuah GPR |
call proc |
instruksi ini digunakan untuk memanggil sebuah prosedur, proc adalah label (nama) dari prosedur yang akan kita panggil. Prosedur adalah sekumpulan instruksi dengan fungsi tertentu, kadang disebut juga subrutin . |
int num |
instruksi ini untuk memanggil interupsi bios atau sistem operasi dos. Perlu anda perhatikan bahwa jika kita berada pada mode virtual-8086 maka instruksi seperti ini akan tetap menghasilkan keluaran, tetapi keluaran tersebut sebenarnya adalah hasil emulasi. num adalah nomor dari interupsi yang akan dipanggil |
xor dest,src |
instruksi ini melakukan bitwise xor terhadap src dan dest dan hasilnya disimpan pada dest. src dan dest biasanya merupakan GPR. |
lea dest,src |
instruksi ini menghitung alamat efektif (effective address) dari src dan hasilnya di simpan pada dest. Instruksi ini biasanya digunakan pada operasi string, dest biasanya adalah register si, dan src adalah variabel yang akan dicari alamat efektifnya. |
and dest,src |
instruksi ini melakukan bitwise and terhadap src dan dest dan hasilnya disimpan pada dest. src dan dest biasanya merupakan GPR. |
or dest,src |
instruksi ini melakukan bitwise or terhadap src dan dest dan hasilnya disimpan pada dest. src dan dest biasanya merupakan GPR. |
out dest,src |
instruksi ini adalah instruksi I/O yang mengirim data ke alamat I/O(device), src biasanya register al,ax atau eax yang isinya adalah data yang akan dikirim, dest biasanya adalah register dx yang menunjukkan alamat yang akan dikirimi data. |
in dest,src |
instruksi ini adalah instruksi I/O yang memasukkan data dari alamat I/O (device), src biasanya register dx yang isinya adalah alamat I/O yang datanya akan diambil, dest biasanya adalah register al,ax atau eax, tergantung ukuran operand-nya (data yang akan diterima). |
retn |
Menghentikan eksekusi program dan mengembalikan kontrol program ke instruksi sesudah instruksi pemanggilan rutin ini (sesudah instruksi yang call atau jump yang memanggil rutin ini). Jika bagian sebelum retn ini tidak dipanggil oleh rutin lain maka program diakhiri (sistem operasi mengambil alih kembali kontrol atas komputer). |
cmp dest,src |
instruksi ini digunakan untuk membandingkan dest dan src, jika hasilnya sama maka Zero Flag pada flag register akan di set (diubah menjadi 1). |
Untuk instruction set yang lain, anda dapat lihat pada Intel Software Developer's Manual volume 1: Basic Architecture dan Intel Software Developer's Manual volume 3: System Programming Guide.
Obrolan bebas:
"Banyak orang berpikir bahwa di jaman seperti ini, saat HLL(High Level Language) seperti C++, Java atau Visual Basic 'berkuasa', pemrograman dengan bahasa assembly 'sudah mati'. Banyak juga orang berpikir untuk apa kita bersusah-susah belajar assembly, kalau HLL sudah ada? Jawaban penulis adalah, saat kecepatan menjadi hal yang sangat krusial maka tak ada yang dapat mengalahkan programming menggunakan bahasa assembly, dan satu lagi, tidak selamanya apa yang kita inginkan dapat kita peroleh dengan HLL. Penulis sendiri sudah beberapa kali menemukan kasus dimana kemampuan mengutak-atik hardware dalam software yang penulis buat tidak dapat disediakan oleh bahasa C atau bahasa lain dalam bentuk API (Application Programming Interface) pada sistem operasi, sehingga pemrograman assembly harus dilakukan. Jadi kita harus ingat bahwa 'Assembly Programming never die'"
Program Assembly Sederhana
Pada bagian sebelumnya kita telah mempelajari beberapa sintaks assembly, sekarang
saatnya menggunakan sintaks tersebut untuk membuat sebuah program sederhana.
Program ini akan menampilkan sebuah string pada console jika dijalankan. Berikut
ini adalah source codenya:
.386
.MODEL TINY
CSEG SEGMENT PARA PUBLIC USE16 'CODE'
ASSUME CS:CSEG
org 100h
start:
;;Routine utama
pushfd ;save flag
pushad ;save isi semua GPR
;; Menampilkan karakter dilakukan melalui interrupt 10h, service 0Eh
;; dari bios video card, karakter yang akan ditampilkan diberikan
;; melalui register si
lea si,msg
mov ah,0Eh ;gunakan service 0Eh
mov bl,07h ;warna foreground
xor bh,bh ;gunakan page 0, akan muncul bug tanpa ini
MORE_DIS:mov al,cs:0+[si] ;karakter yang akan ditulis ada pada al
cmp al,'$' ;'$' menandakan akhir string
je NO_MORE_DIS
inc si
int 10h ;panggil int 10h (serice dari video bios)
jmp MORE_DIS
NO_MORE_DIS:
popad ;restore semua GPR
popfd ;restore semua flags
retn ;return / akhiri program
;;Definisi Variabel
msg:
DB 10,13
DB 10,13
DB 0,0,0,0,0,0,0,'Your wish is my command',10,13,10,13
DB 0,0,0,0,0,0,0,'Tweaking your chipset...',10,13,'$'
CSEG ENDS
Source code di atas adalah source code dalam masm (Microsoft Assembler).
Untuk membuat file *.com dari source code di atas, anda harus meletakkan source
code di atas satu direktori dengan ML.exe dan Link.exe dari Masm (penulis menggunakan
Masm 6.11) kemudian mengunakan perintah berikut di dalam console windows pada
direktori source code tersebut, jika anda belum ada pada direktori source code
tsb maka cd ke direktori itu:
end start
ml /AT sourcecode.asm /link /TINY
dengan sourcecode.asm adalah source code tersebut. Anda dapat meng-copy source
code di atas, kemudian membukanya dengan Notepad.exe , kemudian menyimpannya
dengan ekstensi *.asm. Anda dapat menggunakan editor lain, tetapi editor tersebut
harus "netral", maksudnya tidak menambahkan formatting character ke
dalam source code tersebut, misalnya MS Word tak dapat digunakan sebab menyalahi
aturan ini (menambahkan formatting character). Program di atas akan menghasilkan
output:
Your wish is my command
Tweaking your chipset...
Jadi program yang sangat sederhana ini hanya menampilkan sebuah string di layar
(console) saat dieksekusi. Sekarang kita akan membahas apa saja yang dilakukan
oleh source code ini. Pada bagian awal source code ini anda melihat:
.386
Sintaks ini maksudnya adalah machine code yang dihasilkan oleh program ini hanya
kompatibel (dapat dieksekusi) pada processor i386 atau yang lebih baik. Kemudian
sintaks:
.MODEL TINY
maksudnya adalah kita akan membuat sebuah file *.com. File *.com sebenarnya
adalah file flat binary, yaitu machine code yang dihasilkan langsung dapat dijalankan.
File *.com hanya menggunakan satu segment pada real mode atau mode virtual-8086,
itulah sebabnya pada bagian awal program penulis mengatakan bahwa kita tidak
perlu pusing dengan segment register sebab kita hanya menggunakan satu segmen
saja. Selanjutnya, sintaks:
CSEG SEGMENT PARA PUBLIC USE16 'CODE'
ASSUME CS:CSEG
artinya adalah kita menginginkan agar assembler membuat penyataan-pernyataan
selanjutnya berada dalam satu segmen saat dibuat machine code yaitu pada CSEG,
PARA artinya machine code yang dihasilkan harus diurutkan dalam format 16 bit.
PUBLIC USE16 'CODE' artinya adalah machine code yang dihasilkan akan berjalan
secara default dalam processor x86 pada real mode (sebab pada mode ini, ukuran
register secara default adalah 16 bit), dan segmen sesudah pernyataan ini adalah
segmen kode program (lihat kembali penjelasan segment register yaitu CS dan
DS). Pada bagian akhir program anda melihat sintaks:
CSEG ENDS
sintaks ini memberitahu kepada masm bahwa kode program untuk code segment (yang
berawal pada CSEG ... )hanya sampai pada bagian ini saja. Kemudian pernyataan:
org 100h
artinya adalah machine code ini akan ditempatkan pada memory dengan alamat 100h+alamat
segmen yang ditunjuk oleh CS. Pernyataan:
start:
adalah sebuah label untuk menandai awal program. Dalam masm, anda harus memberikan
sebuah label untuk menandai bagian awal instruksi yang dapat diubah menjadi
machine code. Pada bagian akhir program anda melihat sintaks:
end start
sintaks ini untuk menandai bagian akhir dari program yang kita buat. Kedua penanda
(marker) ini harus ada agar masm dapat bekerja. Sintaks:
pushfd
pushad
sintaks ini artinya: menyimpan isi dari GPR dan Flag Register ke stack. Tujuannya
adalah untuk menghindari kesalahan (bug) saat pernyataan-pernyataan dibawahnya
selesai dieksekusi dan kendali komputer kembali ke sistem operasi. Sebelum memberikan
kendali ke sistem operasi, program ini me-restore kembali nilai GPR dan flag
yang lama dengan instruksi:
popad
popfd
sehingga bug diharapkan tidak akan muncul. Instruksi-instruksi selanjutnya adalah
instruksi untuk menampilkan string. String ini disimpan pada sebuah variabel
bertipe db (byte). Hal ini dilakukan pada baris:
msg:
DB 10,13
DB 10,13
DB 0,0,0,0,0,0,0,'Your wish is my command',10,13,10,13
DB 0,0,0,0,0,0,0,'Tweaking your chipset...',10,13,'$'
baris-baris program ini artinya, kita menyatakan sejumlah variabel bertipe byte
dengan msg digunakan sebagai label untuk menandai alamat awal dari kumpulan
variabel bertipe byte ini. Pernyataan DB 10,13 adalah sebuah pernyataan definisi
dua buah variabel bertipe byte yang nilainya diinisialisasi menjadi 10 dan 13,
dan seterusnya dengan variabel-variabel selanjutnya. Variabel terakhir dari
kumpulan byte (kumpulan byte seperti ini biasa disebut string sebab isinya adalah
karakter) ini berisi karakter '$'. Nilai 10,13 berarti karakter CR(Carriage
return/Enter) dan LF (Line Feed / baris yang baru). Nilai 0 artinya karakter
kosong. String ini ditampilkan dengan perintah:
lea si,msg ;hitung dan pindahkan alamat label msg
;ke register si
mov ah,0Eh ;gunakan service 0Eh
mov bl,07h ;warna foreground
xor bh,bh ;gunakan page 0, akan muncul bug tanpa ini
MORE_DIS: mov al,cs:0+[si] ;karakter yang akan ditulis ada pada
al
cmp al,'$'
;'$' menandakan akhir string
je NO_MORE_DIS
inc
si
int
10h ;panggil int 10h (serice dari video bios)
jmp MORE_DIS
NO_MORE_DIS:
Pada potongan source code di atas, tampilan pada layar dilakukan
dengan menggunakan bantuan bios VGA card. Hal ini dilakukan dengan cara memanggil
interupsi13 nomor 10h dengan register ah bernilai 0Eh, interupsi
ini akan memanggil kode program pada bios vga card untuk menampilkan karakter
yang berada pada register al. Register yang dicek oleh interupsi ini (int 10h)
selengkapnya sbb:
AH = 0Eh
AL = karakter yang akan ditampilkan
BH = nomor page (halaman), digunakan jika kita menggunakan banyak halaman
BL = warna foreground (hanya pada mode grafik)
Jadi baris program int 10h maksudnya adalah tampilkan karakter dan setting yang
sudah kita siapkan (pada register-register yang dicek oleh interupsi ini) ke
layar. Untuk menampilkan seluruh karakter digunakan loop pada label MORE_DIS
s/d NO_MORE_DIS, di dalam loop ini, karakter yang alamatnya ditunjukkan oleh
gabungan register cs dan si dipindahkan terus menerus ke al, dan ditampilkan
dengan memanggil interupsi 10h. Hal ini terus diulang sampai karakter yang ada
pada al adalah '$', hal ini dapat dilihat pada instruksi cmp al,'$' , saat karakter
ini diperoleh (yaitu pada saat register si menunjuk ke variabel terakhir setelah
label msg), program akan keluar ke label NO_MORE_DIS, sebab baris je (jump if
equal) akan membuat eksekusi program berpinda ke label NO_MORE_DIS. Jika anda
masih bingung, baca kembali deskripsi tentang istruction set yang ada pada bagian
sebelumnya untuk memahami apa saja yang dilakukan oleh setiap instruksi.
Catatan:
13Interupsi adalah sebuah mekanisme untuk mengalihkan eksekusi program untuk menjalankan kode program yang sudah disediakan oleh bios atau sistem operasi atau hardware (microprocessor), kode program ini biasa disebut Interrupt Handler. Pada saat interupsi terjadi, "state" processor, yaitu data-data yang ada pada register-register semuanya di simpan pada stack dan sistem mengeksekusi interrupt handler. Interrupt handler menyediakan "service" kepada software yang kita buat untuk hal-hal tertentu yang sering dilakukan,misalnya tampilan ke layar atau membaca sesuatu dari hard drive, dan lain-lain. Untuk mengetahui tentang nomor interupsi dan apa yang dilakukan, silahkan mencari daftar interupsi di buku pemrograman atau di web, jika anda ingin mencari di web maka gunakan search engine dengan keyword "Ralph Brown interrupt list" atau "interrupt list", dengan keyword ini kemungkinan besar anda akan sampai ke situs yang menyimpan interrrupt list dari Ralph Brown. Daftar inilah yang, merupakan daftar interupsi paling lengkap untuk x86.
Obrolan bebas:
"Penulis mohon maaf karena pada tulisan kali ini belum dapat membahas penggunaan Nasm untuk program di atas. Hal ini disebabkan oleh waktu yang terbatas untuk membuat tutorial ini."
Penggunaan Assembly dan C dalam satu Program
Pada bagian ini kita akan membahas bagaimana menggunakan sintaks bahasa assembly
di dalam sebuah program yang menggunakan bahasa C. Untuk memulai pembahasan,
kita akan membedah potongan source code driver yang telah kita bahas sebagian
sebelumnya.
void PatchPCI(ULONG reg_addr, ULONG mask)
{
//Patch the chipset
__asm
{
pushfd ;//save all flags and regs
pushad
mov eax,reg_addr ;//fetch the address of the regs to be patched
mov dx,PCI_ADDR_PORT ;//fetch the input port addr of PCI cfg space
out dx,eax
mov dx,PCI_DATA_PORT
in eax,dx
or eax,mask ;//mask the regs value (activate certn. bits)
out dx,eax
popad ;//restore all flags
Pada source code di atas ada keyword baru, yaitu
popfd
}
}
__asm
.
Keyword ini digunakan untuk memberi tahu compiler Visual C++ bahwa instruksi
pada pernyataan atau blok pernyataan selanjutnya adalah instruksi assembly (ingat
kembali bahwa blok pernyataan dibatasi oleh kurung kurawal / {}). Jadi, hanya
keyword (ditambah {} jika anda ingin mengeksekusi blok pernyataan assembly)
ini saja yang anda butuhkan untuk menambahkan instruksi assembly ke source code
anda.
Selanjutnya aspek teknis yang harus anda perhatikan adalah, source code di atas
akan berjalan pada device driver dalam windows. Jadi microprocessor kita telah
berada pada protected mode, dengan demikian ukuran default register-register
yang tersedia bagi programer assembly adalah 32 bit, jadi GPR yang tersedia
adalah EAX, ECX, EBX,EDX, dst. Sekarang pembahasan source code di atas. Dua
instruksi pertama dan dua instruksi terakhir sudah dibahas sebelumnya. Instruksi
selanjutnya adalah:
mov eax,reg_addr
instruksi ini memindahkan isi variabel reg_addr
ke register EAX,
reg_addr
adalah variabel input yang diberikan oleh fungsi yang
memanggil fungsi PatchPCI. Instruksi selanjutnya, yaitu:
mov dx,PCI_ADDR_PORT
mengisi register dx dengan konstanta PCI_ADDR_PORT. Instruksi berikutnya:
out dx,eax
mengirimkan data yang ada pada register eax ke alamat yang ditunjuk oleh register
dx (pada baris sebelumnya alamat ini telah dimasukkan). Instruksi selanjutnya
telah kita bahas sebelumnya. Kemudian instruksi:
in eax,dx
mengambil data dari alamat yang ditunjukkan oleh register dx. Instruksi berikutnya:
or eax,mask
melakukan operasi bitwise or dengan variabel mask yang diperoleh dari parameter
kedua fungsi PatchPCI
, dan hasilnya disimpan pada register eax.
Baris-baris selanjutnya dapat anda pahami dari pembahasan sebelumnya. Untuk
memperjelas konsep yang sudah anda pelajari, berikut ini diberikan sebuah contoh
program sederhana.
#include < stdio.h >
typedef unsigned int UINT;
UINT tambah(UINT suku1, UINT suku2);
void main()
{
UINT suku1;
UINT suku2;
UINT hasil_operasi;
printf("ketikkan suku pertama : ");
scanf("%d",&suku1);
printf("ketikkan suku kedua : ");
scanf("%d",&suku2);
hasil_operasi= tambah(suku1,suku2);
printf("Hasil penjumlahan kedua suku itu adalah : %d \n",
hasil_operasi);
}
UINT tambah(UINT suku1, UINT suku2)
{
UINT hasil;
__asm
{
pushfd ;/* simpan isi register flag */
pushad ;/* simpan isi GPR*/
mov eax,suku1 ;/* kopi suku1 ke eax 8 */
add eax,suku2 ;/* jumlahkan suku2 ke suku1, simpan hasil di eax */
mov hasil,eax ;/* kopi hasil penjumlahan ke variabel hasil*/
popad ;/* restore kembali isi GPR */
popfd ;/* restore kembali isi register flag */
}
return hasil;
}
Contoh keluaran dari program ini adalah:
ketikkan suku pertama : 5
ketikkan suku kedua : 9
Hasil penjumlahan kedua suku itu adalah : 14
Program di atas dapat anda coba. Source code tersebut tidak akan dijelaskan
lagi oleh penulis sebab cukup mudah dipahami dan komentar yang ada pada source
code nya cukup jelas. Sampai di sini anda sudah dapat membuat sebuah program
yang memanfaatkan bahasa assembly dan C sekaligus, tetapi perhatikan bahwa sintaks
di atas hanya berlaku untuk Visual C++, untuk Software Development tool yang
berbeda, anda harus membaca lagi dukumentasinya.
Obrolan bebas:
"Selamat! Sekarang anda sudah dapat membuat program dengan bahasa C dan assembly, anda tinggal berlatih untuk membuat diri anda semakin terampil :). Untuk memperdalam ilmu anda, anda dapat membaca referensi yang penulis cantumkan. Penulis menyadari bahwa pada tutorial ini masih banyak yang kurang jadi tutorial ini akan diupdate jika penulis sudah punya waktu lagi agar semakin bermanfaat dan mudah dipahami."
Beberapa Sumber Informasi
Berikut ini adalah beberapa sumber informasi yang berkaitan dengan tutorial ini, dan anda dapat memperoleh penjelasan tambahan pada sumber-sumber tersebut.
- Buku The Art
of Assembly Language Programming. Buku ini adalah buku standar bagi para
pemrogram Assembly x86. Jika link ini ternyata sudah tidak ada lagi, anda
dapat mencari pada search engine dengan keyword "The Art of Assembly".
Buku ini adalah eBook yang juga di hosting oleh beberapa situs programming
lain selain link di atas.
- Situs nasm.sourceforge.net. Pada
situs ini anda dapat mendownload sebuah assembler free, yaitu Netwide Assembler
(Nasm). Nasm tersedia dalam dua versi, untuk sistem operasi Linux dan Windows,
pada kedua versi tersebut terdapat tutorial yang cukup komprehensif pada file
help yang di sediakan. Penulis kadang-kadang menggunakan assembler ini, programnya
sendiri sangat bagus, bahkan dalam beberapa segi masih lebih baik di banding
Tasm (Turbo Assembler) buatan Borland.
- Buku Compilers Principles, Techniques, and Tools, Alfred V. Aho,
Ravi Sethi, Jeffrey D. Ullman ,Addison Wesley 1986. Buku ini kadang disebut
sebagai "The Dragon Book" oleh para software developer, memang sampul
bukunya bergambar naga. Merupakan referensi yang sangat baik bagi para programmer
yang ingin tahu cara kerja compiler dan mungkin ingin membuat compiler sendiri.
- Buku C For Scientist and Engineers International Edition, Richard
Johnsonbaugh and Martin Kalin,Prentice Hall, Inc. , 1997.
- Buku Teach Yourself C++ in 21 Days Second Edition, SAMS Publishing
1997. Pada buku ini Chapter 5 dan 10 ada beberapa uraian yang akan sangat
membantu anda memahami konsep pointer.
- Situs www.sandpile.org. Situs ini
menyimpan beberapa data teknis microprocessor x86.
- Situs www.x86.org. Situs ini menyimpan
beberapa data teknis microprocessor x86.
- Situswww.arstechnica.com. Situs
ini memiliki beberapa tulisan teknis yang sangat bagus tentang arsitektur
microprocessor pada bagian CPU Theory and Praxis.
- Situs msdn.microsoft.com. Situs ini adalah situs rujukan para developer software untuk produk microsoft. Sebagian isi dari situs ini dapat anda peroleh pada CD MSDN.
Credits
Penulis mengucapkan terima kasih kepada:
- Donovan Dennis, atas early feedback-nya terhadap artikel Tutorial Membuat Driver ...
- Fritz Edison a.k.a Mitz, juga atas early feedback-nya terhadap artikel Tutorial Membuat Driver ...
- Vavan a.k.a Aco, yang memberi feedback terhadap tutorial ini selama masa pembuatan yang cukup melelahkan
- Kru OCindo, thanks a lot for hosting my articles :)
- Peserta polling pada forum OCindo, thanks atas feedback anda semua :)
copyright © 2003,2004,2005,2006 Darmawan M S a.k.a Pinczakko
0 Komentar