Jump to content
Sign in to follow this  
Ancient One

Bermain Dengan Prapemproses C...

Recommended Posts

Kat sini aku nak citer pasal prapemproses C. Kiter biasa gunakan arahan prapemproses seperti #include, #define dan sebagainya. Semer arahan prapemproses dimulakan dengan simbol # (nilai ascii 35). Secara ringkasnya, prapemproses adalah pemproses teks. Prapemprosesan sentiasa berlaku sebelum kod kita dikompil. Actually ader beberapa fasa dalam proses pembinaan program dengan pengkompil C, biasanya dimulakan dengan proses tokenization, di mana setiap baris kod dipisahkan kepada tokens dan white spaces cam <tab>, <space> dan sebagainya diabaikan. Aper yang penting kat sini kiter patut tau yang fasa prapemproses (terhadap keseluruhan fail sumber) akan berlaku terlebih dahulu sebelum kod diterjemahkan kepada kod mesin.

Untuk membuat citer aku ni lebih bermakna, kenala bagi contoh sikit. Kat sini, aku gunakan M$ nyer C/C++ optimizing compiler (dari Visual C++ 6.0 atau Visual C++ .NET, maner2 laa yang korang suka). Memandangkan aku cuma nak citer pasal prapemproses, aku cuma gunakan satu command line switch untuk cl.exe, iaitu -EP (preprocess to stdout, no #line), yang membolehkan kiter melihat hasil daripada prapemproses pada console. Oleh kerana contoh yang aku berikan banyak yang tak valid untuk diterjemahkan, so, kiter takyah laa kompil kod kiter. Kalau korang gunakan pengkompil yang lain, make sure korang tau switch yang mempunyai persamaan dengan -EP. Biasanya setiap pengkompil C ader feature nih. Check manual. Kalau takde.. nasib korang laa.. baik tukar compiler.

Meh kiter tengok contoh pertama :

#define HELLO_WORLD Hello World!

HELLO_WORLD
Kalau kiter preprocess kod kat atas,
D:\Ancient One\Desktop>cl /EP Scratch.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8168 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

Scratch.c



Hello World!

D:\Ancient One\Desktop>
Teks Hello World! yang terpapar pada konsol adalah hasil prapemproses tadi (kalau korang nak letak output ni dalam fail, bleh gunakan switch -P, atau redirect output kat atas kepada fail, e.g cl /EP Scratch.c > Scratch.pp. Contoh di atas memberikan contoh untuk arahan prapemproses #define. Arahan ni kiter biasa gunakan untuk mentakrifkan pemalar simbolik, contohnya dengan #define MAX_NAME_LENGTH 512, kiter bleh gunakan nama MAX_NAME_LENGTH untuk menggantikan pemalar 512 dalam source code. Arahan #define perlu diikuti dengan identifier name yang valid. Kemudian ia boleh diikuti dengan makna identifier tersebut (di mana ia akan digantikan dengan nilai ni). Contohnya
#define __MSVC__
mentakrifkan simbol __MSVC__ tanpa nilai, iaitu jika ia dirujuk di mana2 bahagian kod sumber, ia akan digantikan dengan tiada apa2. Contohnya
#define HELLO_WORLD

,HELLO_WORLD,
hanya akan menghasilkan ,, sahaja. Takrifan sebegini mempunyai kegunaannya yang tertentu (aku bincang kat arahan #if karang). Akhir sekali, kiter takleh define nama yang sama lebih daripada sekali, kecuali dengan makna yang sama. Contohnya
#define FOO FOO BAR
#define FOO FOO BAR
takde masalah. Tapi
#define FOO FOO BAR
#define FOO FOO FOO
akan menyebabkan ralat. Actually sesetengah compiler hanya akan menyebabkan warning. Dalam kes ni, FOO akan mempunyai nilai FOO FOO (definition terkini). Tu definition yang simple jer. Actually korang jugak bleh define macro dengan arahan #define ni, di mana ia boleh menerima parameters. Nama dalam formal parameters ni akan ditukar dengan nilainya dalam takrifan makro tersebut. Contohnya
#define NAMA_PENUH(first, last) Nama saya ialah first last

NAMA_PENUH(Ancient, One)
akan menghasilkan Nama saya ialah Ancient One. First parameter dirujuk dengan nama first dan setiap kali simbol ni ditemui dalam takrifan makro, ia akan digantikan dengan nilai sebenar yang diberikan semasa makro ini dipanggil. Dalam contoh di atas, first diberikan nilai Ancient dan last diberikan dengan nilai One. Kiter bleh panggil makro ni dengan parameter berlainan dan ia akan digantikan dengan nilai tersebut. Makro bleh dipanggil di mana2 bahagian kod selepas makro ini ditakrifkan, kecuali jika ia adalah arahan prapemproses. Maknanya korang blehla panggil makro dalam takrifan makro yang lain. Contohnya
#define NAMA_PENUH(first, last) Nama saya ialah first last
#define NAMA_DAN_UMUR(first, last, age) NAMA_PENUH(first, last). Umur Saya ialah age

NAMA_DAN_UMUR(Ancient, One, 75)
Makro NAMA_DAN_UMUR memanggil makro yang telah ditakrifkan sebelumnya, iaitu makro NAMA_PENUH. Memandangkan makro sentiasa diproses dahulu, maka kalau kiter ader 2 identifier, satu untuk makro dan satu untuk takrifan fungsi, rujukan kepada identifier tersebut sentiasa memanggil makro tersebut. Contohnya
void foo(int x, int y) {}
#define foo(x,y) foo(x,y);

foo(0, 0);
foo(0,0) memanggil macro foo(x,y), iaitu, ia akan digantikan dengan foo(0,0);, biler kod ni kena kompil karang, ia akan memanggil fungsi foo. Dalam takrifan makro, ader beberapa operator yang kiter bleh gunakan, iaitu # (tukar argument kepada string) dan ## (lekatkan argument yang berasingan). Meh kiter tengok contoh kat bawah :
TO_STRING(x) #x

TO_STRING(Hello World)
akan menghasilkan "Hello World" operator ni bukan just kurungkan argument yang diberikan dengan "". Tapi ia menukarkan argument kepada string yang sah seperti yang diperlukan dalam C language. Ini bermakna, ia akan meletakkan "\" di mana2 yang patut, korang bleh cuba dengan memberikan argument seperti TO_STRING("Hello World"). Operator ## plak lekatkan arguments. Contohnya
#define NAME(first, last) first##last

NAME(Ancient, One)
akan menghasilkan AncientOne. Korang takdela bleh lekat dua argument jer. Malah bleh lekat brape banyak yang korang suka, dan aper2 simbol pun bleh. Tapi korang takleh la lekat hasil panggilan makro yang lain plak. Contohnya
#define BAR(x, y) x##y
#define FOO(x, y) x##y## BAR(x,y)

FOO(Ancient, One)
akan menghasilkan AncientOneBAR(Ancient,One), dan bukannya AncientOneAncientOne. Tu jer laa pasal makro dan arahan #define secara keseluruhannya. Sebelum aku pegi lebih lanjut, aku nak citer sikit pasal trigraph. Kalau korang jumpe menatang cam ??=, ??(, ??<, etc (yang dimulakan dengan dua tanda soal ??), tu trigraph ler tu. Trigraph ni actually alternatif untuk simbol yang kiter takleh masukkan secara direct ke dalam source code (e.g kat keyboard kiter takde simbol tuh). Trigraph replacement berlaku pada peringkat yang sangat awal, sebelum prapemprosesan. Pendek kata sebelum aper2 proses berlaku. Ia akan digantikan dengan simbolnya, tak kire la kat dalam string ker. Selagi sequence of characters tu dikenali sebagai salah satu simbol trigraph, ia akan ditukar. Most of the time kiter tak akan guna menatang nih tapi aku citer gak supaya korang tak raser pelik bila string "Unknown Error ??!" korang jadik "Unknown Error |". Contoh :
#define CONTOH_TRIGRAPH Turutan simbol ?, ? dan = mewakili aksara ??=
CONTOH_TRIGRAPH
Errr.. sebelum aku terlupa.. ANSI C ader define beberapa predefined macros (prapemproses tau kewujudan macros ni walaupun ia tak ditakrifkan di mana2). Antaranya adalah __DATE__, __FILE__, __LINE__, __STDC__, __TIME__ dan __TIMESTAMP__. Predefined macros camni penting supaya korang bleh gunakannya seawal yang mungkin di dalam source file. Contohnya, macro __LINE__ mewakili current line number dalam source :
Baris ini : __LINE__
Baris ini plak : __LINE__
akan menghasilkan
Baris ini : 1
Baris ini plak : 2
Sesetengah compiler jugak ader predefined macros yang tersendiri. Biasanya diaorang ader define pemalar yang membolehkan korang tau pengkompil aper yang digunakan untuk mengkompil kod korang, versi pengkompil tu, pelantar di mana kod korang dikompil dan sebagainya. Contohnya (contoh jer, nama sebenar pemalar aku tak sure) :
#if defined(__MSVC__)
  ...
#elif defined(__GCC__)
  ...
#elif defined(__ICC__)
  ...
#else
  ...
#endif

membolehkan korang letak kod yang bleh dikompil oleh visual c++, gcc dan intel compiler dengan betul, contohnya. Arahan #if ngan #elif, #else, #endif tu kiter tengok karang.

Sekarang meh kiter tengok arahan #include plak. Fungsi arahan ni simple jer. Masukkan fail yang dinyatakan pada baris tersebut dan praproses fail ni sebelum pegi ke baris berikutnya. Argument untuk arahan ni adalah nama fail yang perlu dimasukkan dan terdapat 2 cara untuk menyatakannya. Yang pertama sekali, kiter bleh gunakan <>, e.g #include <stdio.h>. Yang kedua plak, kiter leh gunakan "", e.g #include "stdio.h". Dah tentu terdapat perbezaan di antara kedua-dua cara nih. Yang first tadi (<>), kiter suh prapemproses carik fail stdio.h kat search path untuk include directories compiler tersebut. Search path ni kiter leh berikan dengan switch -I. Then kalau tak jumpe dia carik plak kat directory yang kiter nyatakan dalam INCLUDE environment variable (semicolon separated list of path). Kalau tak jumpe gak... abis citer la. Dia takkan search kat current directory (i.e kat direktori yang sama dengan fail sumber di mana arahan #include ni ditemui). Yang kedua plak (""), kiter suh prapemproses carik kat current directory dulu (i.e kat direktori yang sama dengan fail sumber di mana arahan #include ni ditemui), then kalau tak jumpe, barulah dia carik kat search path untuk include directories compiler tersebut. Errr.. penerangan ni takdelaa tepat sangat.. tapi basically camtu laa.... sambung karang plak..

Share this post


Link to post
Share on other sites

Terima kasih Ancient One. Satu artikel yg SANGAT menarik dan berguna..

Share this post


Link to post
Share on other sites

Macamana nak lihat hasilnya.

CONTOHNYA hello world tu, bila dah taip sintaksisnya, camna nak preview?

aku dah bagitau kat atas..

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
Sign in to follow this  

×
×
  • Create New...