Linux ในฐานะ Desktop ของนักพัฒนาโปรแกรม

ก่อนอื่นผมต้องบอกก่อนว่าผมเปลี่ยนมาใช้ Linux เป็นระบบปฎิบัติการณ์หลักบน Laptop ผมมาสักพักใหญ่แล้ว ทั้ง ๆ ที่จริง ๆ ผมมี License ของ Windows อยู่บน PC ทุกเครื่องที่ผมมี (Windows 8 สองเครื่อง Windows 7 หนึ่งเครื่อง)

สาเหตุที่ผมเปลี่ยนเครื่องนี้มาเป็น Linux เป็นเพราะผมต้องการเปลี่ยนบรรยากาศครับ ส่วน PC นั้นผมใช้เล่นเกม + ทำงานเพลงเป็นงานอดิเรก ซึ่งซอฟท์แวร์ที่ใช้ทั้งหมดยังรันบน Windows ส่วนเครื่อง Laptop นั้นผมใช้กับงานทั่ว ๆ ไปกับการเขียนโปรแกรม

สำหรับการใช้งานทั่ว ๆ ไปนั้นผมแทบไม่มีความจำเป็นที่จะต้องใช้ Windows เลย เพราะแอพลิเคชั่นที่ใช้นั้นแทบทั้งหมดคือ Web Browser (หรืออีกนัยหนึ่งก็คือผมใช้งานพวก Web Application เป็นหลักนั่นเอง) แม้กระทั่งเวลาฟังเพลงผมยังเปิด Deezer เลย สำหรับงานด้าน Office นั้นผมไม่ได้ใช้ Microsoft Office ในงานส่วนตัวเลย เพราะพบว่าไม่มีความจำเป็น Google Docs ตอบโจทย์งานที่จำเป็นกับการใช้งานส่วนตัวได้เป็นอย่างดี และถ้าจำเป็นจริง ๆ ผมยังสามารถพึ่งพา LibreOffice ได้ ทั้งนี้ทั้งเครื่อง PC (Windows 8) และ Laptop นั้นไม่มี Microsoft Office ติดตั้งเอาไว้ครับ คือ ไม่ได้ซื้อไว้น่ะครับ

ส่วนงานเขียนโปรแกรมเนี่ย เกือบครึ่งผมทำบน Java ครับ หลาย ๆ คนอาจจะมองว่ามันช้า ภาษาเก่า มีปัญหาด้านความปลอดภัย และอะไรอีกมากมายร้อยแปด แต่สำหรับการเขียนโปรแกรมใช้งานทั่ว ๆ ไปนั้น Java ทำได้ดีในแบบฉบับของมัน งานส่วนใหญ่แทบไม่ต้องใช้ Library เสริม และถ้าต้องใช้ก็หาได้ไม่ยากเพราะเป็นภาษา/แพลตฟอร์มที่ได้รับความนิยม ทั้งนี้ Java เป็นภาษาที่ผมเชี่ยวชาญที่สุดครับ

ยิ่งถ้าเป็นงานที่ไม่ต้องส่ง เป็นการเขียนใช้เองเนี่ย ผมจะใช้ Xtend เป็นหลัก เยอะกว่าการใช้ภาษา Java ตรง ๆ เสียอีก เพราะว่ามันทำงานบน Java Platform ทำให้มีความสามารถสูง ในขณะเดียวกันการเขียนก็ทำได้เร็วกว่า Java เพราะว่าต้องเขียนโค้ด Boilerplate น้อยกว่า Java มาก การใช้งานกับ Library อื่น ๆ ก็ทำได้อย่างไม่มีปัญหา อาจจะช้ากว่า Java บ้างแต่ผมก็ไม่คิดว่ามันช้าเกินไปหรอกนะครับ

ทั้งนี้ผมใช้ Xtend/Java บนทั้ง Desktop App (Swing), Console App และ Web App (Vaadin) ครับ

ส่วนอีกเกือบครึ่งที่ตอนนี้มันเป็นโปรเจคทดลองของผมนั้นเขียนบน C++ เพราะว่าผมก็ยังคงทำงานเป็นโปรแกรมเมอร์ C++ อยู่ แน่นอนว่าบน Linux นั้นไม่มี Visual Studio ซึ่งก็ทำให้ทำงานลำบากในแง่หนึ่ง แต่ก็จะบอกว่าก็สบายในอีกแง่หนึ่ง (ฮา) สมัยเรียนผมเริ่มเขียนโปรแกรมจากบน Console คือใช้ pico/nano เขียนโค้ดใน Text Editor บน Terminal ที่ต่อเข้ากับ Server ที่เป็น Solaris แล้วใช้คอมไพล์เลอร์บนนั้นสั่งคอมไพล์ครับ (ซึ่งก็เป็น Command Line เหมือน GCC/Clang แหละ) (คลาสอื่นใช้ Turbo C) ดังนั้นผมก็เลยไม่จำเป็นต้องใช้ IDE ในการเขียนโค้ด ถึงจะยังใช้ Debugger บน Console (เช่น GDB) ไม่เป็นก็เถอะ

แต่เอาเข้าจริง ๆ ผมก็ใช้ IDE อย่าง Eclipse (ร่วมกับปลั้กอินที่ชื่อว่า C++ Development Tooling (CDT)) นั่นล่ะครับ ก็พอจะดีบั้กได้ไม่ลำบากเกินไป (แต่คนใช้ Eclipse จะรู้ว่าบางทีก็รู้สึกลำบากชีวิตเช่นกัน) มี IDE หลายตัวที่ทำงานบน Linux อย่างที่เขาว่าเวิร์คตัวหนึ่งก็คือ Qt Creator

สำหรับแง่มุมหนึ่งที่ผมไม่ค่อยชอบเกี่ยวกับ Windows/Visual Studio ก็คือการไม่มีระบบรองรับ Library ที่ใช้งานได้สะดวก ทั้งหมดอาศัยการตั้ง Path รายโปรเจค ซึ่งผมเขียนโปรเจคใหม่ค่อนข้างใหม่ก็ต้องง่วนกับการคอมไพล์ไลบราลี + เซ็ตพาธเพื่อให้ใช้งานได้ และพออัพเกรดทีก็เหนื่อยที บน Linux นั้นจะมีพาธกลางที่เก็บบรรดา Library ทั้งหลาย (/usr/include, /usr/lib และ /usr/local/include, /usr/local/lib) ทำให้ไม่ต้องมานั่งเซ็ตพาธ ใส่ compiler parameter ให้ถูก (ก็แค่ -l<ชื่อlib> ก็ใช้ได้ละ) พอมีไลบราลีเวอร์ชั่นใหม่ก็แค่ติดตั้งใหม่ก็จบ ไม่ต้องมาทำอะไรวุ่นวายทีหลัง

แน่นอนว่าถ้าไม่อยากใช้พาธกลาง อยากเซ็ตไลบราลีเอง ใช้ไลบราลีจากพาธที่ตั้งไว้ก็ทำได้เช่นกันครับ

นอกจากนี้เราสามารถใช้สคริปท์ในการ build อย่าง MAKEFILE ก็ได้ (Visual Studio ก็รองรับ) แต่ส่วนตัวผมจะใช้ CMAKE ช่วยด้วย เป็นสคริปท์ที่ใช้สร้างไฟล์ที่ใช้กับระบบ Build อีกทีครับ (มันสร้าง MAKEFILE, Eclipse Project File หรือแม้กระทั่ง Visual Studio Solution File ได้ จะพูดถึงในโอกาสอื่น ๆ ครับ) ซึ่งการใช้ CMAKE นั้นช่วยให้เราสามารถสร้างโปรแกรมที่ทำงานบนหลาย ๆ แพลตฟอร์มได้โดยไม่ยากจนเกินไป

ทั้งนี้อีกประเด็นหนึ่งสำหรับ Visual Studio ที่ผมไม่ค่อยประทับใจนัก เกี่ยวกับปัญหาของเวอร์ชั่นหลัง ๆ ที่มันติดตั้งฟีเจอร์ที่ผมไม่ได้ใช้มามากเกินไป แถมไปวุ่นวายกับไฟล์ระบบเยอะแยะไปหมด ผมรู้สึกว่าหลังติดตั้ง Visual Studio Express ไปแล้วเครื่องผมมันทำงานแปลก ๆ ไปพอสมควร (ที่แน่ ๆ คือมันบูทช้าลงมาก)

แต่ก็ไม่ได้บอกว่าผมไม่ได้ใช้ Visual Studio เลยนะ ผมใช้ที่ทำงานครับ 555 ใช้กับงานบริษัทครับ งานส่วนตัวไม่เกี่ยว

อ้อ สำหรับงานที่ต้องเขียนให้คนอื่นใช้ (มีบ้าง แต่ไม่เยอะ) ผมก็เขียนโค้ดให้เน้นการทำงานข้าม platform เป็นหลัก แล้วก็จะทดสอบโปรแกรมบน Windows โดยคอมไพล์โปรแกรมบน Cygwin ซึ่งเป็นการจำลองการทำงานของระบบแบบ POSIX ซึ่งก็ใช้งานได้ดีในระดับหนึ่ง หรือไม่ก็คอมไพล์มันบน Visual Studio ก็ได้เช่นกัน (แต่ตอนนี้ผมไม่ได้ติดตั้งไว้ครับ)

ในด้านการเขียนเกมที่เป็นเรื่องที่ผมสนใจ (และเคยทำมาบ้างในอดีต) ผมก็ทำบน Linux เช่นกันนี่แหละ ก็เลี่ยงการใช้ DirectX ที่ไม่มีบน Linux ซึ่งผมเป็นคนที่ไม่ชอบ DirectX เป็นทุนเดิมอยู่แล้วครับ (ผมคิดว่า DirectX เป็น API ที่น่าเกลียดน่ะครับ) ผมใช้ SDL เป็นไลบราลีด้านมัลติมีเดีย คู่กับ OpenGL (ที่ไม่ได้แตะนานแล้วครับ ลืมหมดแล้ว) ถ้าจะเขียนเกม 3D น่ะนะ SDL ทำงานบนหลายแพลตฟอร์มมาก ๆ ถ้าผมจำเป็นจะต้องคอมไพล์ให้คนที่ใช้ Windows ได้ใช้ก็ทำได้ไม่ลำบากนักเช่นกัน แต่ก็มีจุดที่ต้องระวังอยู่บ้าง (ผมพบว่าปัญหาบน Windows มีน้อยกว่านะ)

บ่นแค่นี้ละกัน จริง ๆ คือผมแค่อยากเปลี่ยนบรรยากาศก็เลยเล่าเรื่องสภาพแวดล้อมการทำงานส่วนตัวให้ฟังเล่น ๆ ครับ เดี๋ยวคราวหน้ามาคุยเรื่องทางเทคนิคหนัก ๆ กันต่อ

#include ในภาษา C และ C++ (ครึ่งแรก)

ทุก ๆ คนที่เคยใช้ C/C++ ย่อมเคยที่จะใช้เจ้า preprocessor #include อยู่แล้ว แต่หลายคนคงไม่เข้าใจว่าไอ้เจ้านี่มันทำอะไร วันนี้จะอธิบายเพิ่มเติมเรื่องของเจ้านี่นะครับ

Syntax

สำหรับวิธีการใช้ #include นั้นมีอยู่สองแบบครับ นั่นก็คือ #include <ชื่อไฟล์> และ #include "ชื่อไฟล์" แต่ละแบบมีความหมายที่แตกต่างกัน ก็คือ แบบที่ใช้ <> นั้นเป็นการอ้างอิงถึงไฟล์ที่อยู่ใน include path ที่เราตั้งค่าให้ compiler ไปหา (พูดกันง่าย ๆ ก็บรรดา library ทั้งหลายที่ไม่ได้เป็นส่วนหนึ่งของโปรเจคครับ ซึ่งรวมถึง standard libary ด้วย) กับแบบ "" นั้นจะเป็นการอ้างถึงไฟล์ที่อยู่ในโปรเจคของเรา

ทั้งสองแบบต่างกันแค่ตำแหน่งของไฟล์เท่านั้นครับ แต่จริง ๆ มันทำงานเหมือนกัน ส่วนจะแยกเป็นสองแบบทำไมให้งงนี่ผมเองก็ไม่ทราบเหตุผลจริงๆ เหมือนกัน ถ้าให้เดาก็คงแค่อยากให้ใช้ไฟล์ที่มีชื่อเหมือนกันระหว่างในโปรเจคกับตัว library ที่เราเรียกใช้

การทำงานของ #include

เจ้า preprocessor #include เป็นการสั่งให้ compiler นั้นไปเอาเนื้อหาของไฟล์ที่ระบุไว้มาแทนที่คำสั่งนี้ก่อนที่ไฟล์จะถูกคอมไพล์ครับ ยกตัวอย่างเช่น ถ้าผมมีไฟล์สองไฟล์ดังต่อไปนี้

abc.i

printf("ABC");

usage.cpp

int main(char** args, int argc) {
    #include "include.i"
    return 0;
}

ผมคิดว่าหลาย ๆ คนคงไม่เคยใช้โค้ดลักษณะนี้ แต่น่าจะพอเดาได้ ครับเวลาเราสั่งคอมไพล์ usage.cpp นั้นคอมไพล์เลอร์จะเอาโค้ดจาก abc.i ไปแทนประโยค #include "abc.i" ก่อนที่จะคอมไพล์โค้ดนั้น เราก็จะได้โค้ดหน้าตาแบบนี้ครับ

int main(char** args, int argc) {
printf("ABC");
    return 0;
}

คิดว่าน่าจะเห็นภาพนะครับว่าทำงานอย่างไร

ทั้งนี้การใช้งานลักษณะนี้นี่ไม่ค่อยได้รับความนิยมในระยะหลัง และผมเองก็ไม่แนะนำซะด้วย การใช้ #include ลักษณะนี้ทำให้การไล่โปรแกรมเวลามีปัญหาทำได้ยากมากครับ ผมยืนยันได้ เพราะผมเคยทำงานกับโปรเจคที่ใช้ ไฟล์ include เยอะมากในลักษณะนี้มาก่อน บอกตามตรง มันน่าปวดหัวมากครับ

การใช้งาน #include

การใช้งาน #include ที่ได้รับความนิยมและเป็นที่แนะนำกันก็คือการใช้ ไฟล์ include ในเชิงของการทำ forward declaration ยกตัวอย่างเช่น ถ้าเรามีไฟล์หลายไฟล์ที่ใช้งานฟังก์ชั่น int calculateAge(date birthdate, date currentdate)

ปัญหาของการใช้ Forward Declaration

ถ้าเราไม่ใช้ #include เลยเราก็ต้องประกาศฟังก์ชั่นนี้บนทุกไฟล์ที่อ้างอิงถึง (และกำหนดฟังก์ชั่นนี้บนไฟล์ใดไฟล์หนึ่งเพียงไฟล์เดียว ไม่เช่นนั้นตอน link โปรแกรมจะพังอีก) ทุก ๆ ไฟล์ก็จะมีไอ้การประกาศนี้บนส่วนบนสุดของไฟล์ ก่อนหน้าที่ฟังก์ชั่นนี้จะถูกเรียกใช้งาน

int calculateAge(date birthdate, date currentdate);

นี่แค่ฟังก์ชั่นเดียวนะครับ ถ้ามีหลาย ๆ ฟังก์ชั่นนะ มันจะยาวสุด ๆ ไปเลยครับ อาจจะเป็นแบบนี้

int calculateAge(date birthdate, date currentdate);
int compareDate(date date1, date date2);
...
int hundredthFunctionAboutDate();

ถ้าสมมติว่าผมแก้ signature ของฟังก์ชั่นใดฟังก์ชั่นหนึ่ง ผมต้องไปตามแก้มันหมดทุกไฟล์ … ลาสุนัขบวชเลยล่ะครับ

แก้ปัญหาโดยการใช้ #include

ผมสามารถที่จะย้ายเอาการประกาศฟังก์ชั่นทั้งหมดมาใส่ในไฟล์เดียว แล้วใช้ #include ในทุก ๆ ไฟล์ที่ใช้งานแทน สะดวกไหมครับ ? ผมอาจจะตั้งชื่อไฟล์นี้ว่า datefunction.h แล้วใช้คำสั่ง #include "datefunction.h" ในทุก ๆ ไฟล์ที่ผมจะใช้ฟังก์ชั่นที่ประกาศในไฟล์นี้ (ส่วนตัวฟังก์ชั่นจะกำหนดอยู่ที่ไหนนั่นอีกเรื่องนึงนะ)

ถ้าเกิดผมมีการแก้ไข signature ของฟังก์ชั่น เช่นเปลี่ยนจาก int calculateAge(date birthdate, date currentdate) เป็น float calculate_age(date birthdate, date currentdate) ก็แค่แก้ในไฟล์ datefunction.h เท่านั้นเอง ยกเว้นถ้าเกิดว่ามีบางไฟล์ที่ใช้ float ไม่ได้จริง ๆ ก็ไปแก้ไฟล์นั้นเอาอีกที อะไรทำนองนี้

อ้อ ใน C/C++ เราจะเรียกไฟล์ include ในลักษณะที่เป็น forward declaration ว่า header file ครับ และมักจะใช้นามสกุล .h หรือ .hpp

Inclusion Guard

สำหรับ include ไฟล์นั้นก็เหมือนกับไฟล์ที่มีโค้ดของ C/C++ ตามปรกตินั่นล่ะครับ คือมีได้ทุกอย่างอย่างที่ .c หรือ .cpp มี แน่นอนว่านั่นรวมถึงการเรียกใช้คำสั่ง #include ด้วย

ปัญหามันก็จะมาเมื่อเราใช้ include ไฟล์ที่มี #include ไฟล์อื่น ๆ เข้ามา และไอ้ไฟล์ที่เรียกใช้ก็ดันไปเรียก #include ไฟล์นั้นเข้ามา ก็คือ #include ไฟล์เดียวกันทั้งบนไฟล์ที่เรียกใช้และไฟล์ที่ถูก include เข้ามา เช่น สมมติว่าผมมี

vector.h

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

matrix.h

#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

usage.c

#include "vector.h"
#include "matrix.h"

ผมละโค้ดที่เรียกใช้ใน usage.c ไว้เพราะว่ามันไม่สำคัญนะครับ แต่เรามาดูตอนคอมไพล์กันเลยดีกว่า ตัวคอมไพล์เลอร์นั้นจะทำการแทนที่โค้ดใน usage.c ให้มีลักษณะแบบนี้

struct {
    float x;
    float y;
    float z;
    float w;
} vector;
struct {
    float x;
    float y;
    float z;
    float w;
} vector;

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

กลายเป็นว่าเรามี struct ที่ชื่อว่า vector สองตัว … กลายเป็นว่าคอมไพล์ไม่ผ่านครับ declaration สองตัวที่ชื่อเหมือนกันนั้นอยู่บนไฟล์เดียวกันไม่ได้ครับ กลายเป็นการประกาศซ้ำ

หลายคนคงคิดว่า ก็เอา #include "vector.h" ใน usage.c ออกไปสิ ทำอย่างนั้นก็ได้ครับ แต่สำหรับโปรแกรมที่ซับซ้อนมาก ๆ เราทำแบบนี้ไม่ได้ครับ คือถ้าให้ include สัก 5 ไฟล์ ใน 10 ไฟล์นี่ก็ไล่ไปเถอะครับ ขอให้โชคดี

วิธีแก้ก็คือ เราจะใช้ preprocessor สามตัวช่วยครับ ตามตัวอย่างโค้ดข้างล่างนี้

vector.h

#ifndef VECTOR_H
#define VECTOR_H
struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#endif

matrix.h

#ifndef MATRIX_H
#define MATRIX_H
#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);
#endif

ผมขออธิบายเฉพาะ vector.h นะครับ เริ่มจาก #define ก่อน เจ้า #define นี้เป็นตัวประกาศ macro ชื่อ VECTOR_H อยู่ เป็นการบอกให้คอมไพล์เลอร์รู้ครับ คือในกรณีนี้เราไม่กำหนดว่า macro นี้ทำอะไร บอกแค่ว่าให้สร้างขึ้นมาณ.จุดนี้

ส่วนคำสั่ง #ifndef ย่อมาจาก if not define จะตามท้ายด้วยชื่อ macro เดาจากชื่อคงพอบอกได้ ก็คือถ้ามี macro ชื่อ VECTOR_H ประกาศเอาไว้ตัวคอมไพล์เลอร์จะเอาโค้ดตั้งแต่ประโยค #ifndef จนถึง #endif ออกจากโค้ดก่อนคอมไพล์นั่นเอง

พอเราสั่งคอมไพล์ usage.cpp แล้วเนี่ย คอมไพล์เลอร์จะเริ่มจากประมวลผล preprocessor ที่บรรทัดแรก จะได้โค้ดหน้าตาแบบนี้

usage.cpp

#ifndef VECTOR_H
#define VECTOR_H
struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#endif
#include "matrix.h"

จากนั้นมันจะทำ preprocessor บรรทัดบนสุดต่อ ก็คือ #ifndef VECTOR_H จนถึง #endif (มันใช้คู่กันก็เลยทำงานเป็นคู่ครับ) ซึ่งเนื่องจากณ.จุดนี้ยังไม่มีมาโครที่ชื่อ VECTOR_H ประกาศเอาไว้เลย มันก็เลยคงโค้ดส่วนด้านในระหว่าง #ifndef จนถึง #endif เอาไว้ ผลลัพท์ที่ได้ก็จะเป็นแบบนี้

usage.cpp

#define VECTOR_H
struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#include "matrix.h"

ตัว preprocessor ข้างบนสุดที่เหลือก็คือ #define VECTOR_H ตัว compiler เห็นดังนั้น ก็ทำการสร้าง macro เก็บเอาไว้ในหน่วยความจำของตัวเอง แล้วก็เอาโค้ดบรรทัดนี้ออก ได้ผลลัพท์ออกมาเป็นแบบนี้

usage.cpp

struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#include "matrix.h"

ต่อไปก็มี preprocessor ตัวที่อยู่บนสุดก็คือ #include "matrix.h" คอมไพล์เลอร์ก็จะไปเอาเนื้อความของไฟล์matrix.h มาใส่ตรงนี้

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

#ifndef MATRIX_H
#define MATRIX_H
#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);
#endif

พอถึงตรงนี้น่าจะเดาได้แล้ว ครับ มันทำงานคู่ #ifndef จนถึง #endif ต่อ เนื่องจาก MATRIX_H ไม่ได้ถูกประกาศไว้ มันก็เลยคงโค้ดเอาไว้เหมือนเดิม กลายเป็นแบบนี้

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

#define MATRIX_H
#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

แล้วก็ทำงานที่ประโยค #define MATRIX_H ก็แค่สร้างมาโครขึ้นมาในหน่วยความจำของคอมไพล์เลอร์ แล้วก็ลบโค้ดประโยคนี้ทิ้ง

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

#include "vector.h"

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

คำสั่งต่อไปก็คือ #include “vector.h” ก็แทนค่าเข้าไปเลย

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

#ifndef VECTOR_H
#define VECTOR_H
struct {
    float x;
    float y;
    float z;
    float w;
} vector;
#endif

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

หลังจากนั้นมันก็ทำงานในส่วนของ #ifndef VECTOR_H จนถึง #endif ครับ แต่จำได้ไหมครับว่า เรามีการประกาศ VECTOR_H เอาไว้แล้วข้างบน (ลองย้อนกลับขึ้นไปอ่านดูนะครับ) ดังนั้นโค้ดประโยคนี้จะไม่ทำอะไรเลย โค้ดที่ได้ก็จะกลายเป็นแบบนี้ไป

usage.c

struct {
    float x;
    float y;
    float z;
    float w;
} vector;

struct {
    float x0;
    float y0;
    float z0;
    float w0;
    /*....omitted*/

} matrix;
vector multiply(vector v, matrix m);
vector multiply(matrix m, vector v);

และสุดท้ายพอไม่มี preprocessor เหลืออยู่แล้ว ตัวคอมไพล์เลอร์ถึงจะเริ่มคอมไพล์โค้ดครับ

นี่คือการทำงานของ inclusion guard ซึ่งเป็นส่วนสำคัญที่ header file ควรจะมี เรื่องหนึ่งที่ต้องระวังก็คือเราต้องใช้ชื่อ macro ที่ไม่ซ้ำกันในแต่ละไฟล์ ไม่เช่นนั้นอาจจะมีปัญหาได้ (ใน MSVC มีคำสั่ง #pragma once ที่ทำงานเป็น inclusion guard ได้ ถ้าเราไม่สนใจเรื่อง cross-platform จะใช้ตัวนี้แทนก็ได้ครับ)

ครั้งนี้ผมจะขอจบแค่นี้ก่อน ครึ่งหลังเราจะมาทำความเข้าใจกับปัญหาของ include file และจะมาดูกันว่าเราจะแก้ปัญหานั้นได้อย่างไรครับ