Archive

Archive for the ‘Programming’ Category

ประเภทของ Object ใน Java Virtual Machine และ Common Language Runtime

ใน Java Virtual Machine (JVM) และ Common Language Runtime (CLR) นั้น จะมี Type อยู่สองประเภทคือ Value Type และ Reference Type ซึ่ง Value Type จะเป็น Type ที่ Object จะเกิดการ Copy ตัวมันเองเมื่อมีการ Assign ตัวมันให้กับตัวแปรอื่น ส่วน Reference Type จะเป็น Type ที่ Object อาศัยอยู่บน Heap เท่านั้น ซึ่งต้องใช้ new ในการสร้างขึ้นมา

ใน JVM นั้น เราไม่สามารถสร้าง Value Type เพิ่มได้ ต้องใช้อันมี่ทีอยู่แล้วเท่านั้น ซึ่งแตกต่างจาก CLR ที่เราสามารถสร้าง Value Type เพิ่มได้ ทำให้ CLR มีข้อได้เปรียบอยู่หลายอย่าง เช่น

  • Performance ที่เพิ่มขึ้น เนื่องจาก Value Type ไม่จำเป็นต้องจอง Memory บน Heap
  • ติดต่อกับ Native Code ได้ง่ายขึ้น เนื่องจาก Type ใน Native Code จะเป็น Value Type

Object ที่เป็นชนิด Value Type นั้นสามารถแปลงเป็น Reference Type ได้ด้วยการ Wrap ไว้ใน Object ที่เป็น Reference Type อีกทีหนึ่ง ซึ่งการ Wrap นี่จะเรียกว่า “Boxing” ส่วนการแปลงกลับมาเป็น Value Type อีกครั้งจะเรียกว่า “Unboxing”

การ Boxing นั้น ใน C# จะสามารถ Assign ตัว Object ที่เป็นประเภท Value Type ใส่ตัวแปรที่เป็นคลาส System.Object ได้ทันที การ Unboxing นั้นก็เหมือนกัน สามารถ Cast กลับมาได้ทันที ส่วน Java นั้นเราจำเป็นต้องใช้คลาสที่มีมาให้อยู่แล้ว เช่น java.lang.Integer สำหรับ Wrap ตัว Object ที่เป็นชนิด int

ว่าด้วยเรื่องของ Asynchronous I/O บน Unix และ Win32

Asynchronous I/O ที่ใช้กันส่วนใหญ่ในปัจจุบันจะมีอยู่สองแบบ คือ แบบเก่าที่เป็นแบบ Notify on Ready และแบบใหม่ที่เป็นแบบ Notify on Completion ซึ่งต่อไปนี้จะขอเรียกว่า NoR และ NoC

NoR นั้นได้ถูกคิดค้นขึ้นมาก่อน NoC สำหรับใช้บน Unix ในช่วงแรกๆ ส่วน NoC นั้นได้ถูกคิดค้นขึ้นมาสำหรับใช้กับ Windows NT ซึงถูกเรียกว่า Overlapped I/O (แต่ก็ยังคงรองรับ NoR)

NoR นั้นจะใช้การแจ้งเตือนเมื่อ I/O พร้อมที่จะทำงาน เช่น มีข้อมูลสำหรับอ่านแล้ว หรือมี Buffer ว่างสำหรับทำการเขียน ข้อดีคือ เขียนได้ง่ายกว่าแบบ NoC ส่วนข้อเสียคือ

  • จัดการกับ Error ที่เกิดขึ้นจากการเขียนได้ยากมาก เนื่องจากเราเขียนลง Buffer ของ Kernel ไม่ใช่การเขียนลง Device จริง
  • อ่านพร้อมกันมากกว่าหนึ่งเธรดลำบาก เนื่องจากต้องมีการ Synchronize เพื่อป้องกันไม่ให้เธรดอื่นเข้ามาอ่านในระหว่างที่อีกเธรดกำลังอ่านอยู่ ซึ่งก็ไม่ใช่วิธีที่ดีเพราะจะก่อให้เกิดการ Lock เกิดขึ้น

ส่วน NoC นั้นจะใช้การแจ้งเตือนเมื่อ I/O ทำงานเสร็จแล้ว เช่น เราสั่งอ่านข้อมูล เมื่อการอ่านเสร็จสิ้น ก็จะมีการแจ้งเตือนมาบอกว่าอ่านเสร็จแล้ว การเขียนก็เหมือนกัน เราสั่งเขียน เมื่อการเขียนเสร็จสิ้น ก็จะมีการแจ้งเตือนมาบอกว่าเขียนเสร็จแล้ว มีข้อดีคือ

  • ใช้ได้มากกว่าหนึ่งเธรด เนื่องจากมีการกำหนด Buffer ที่จะทำการอ่านหรือเขียนตอนสั่ง I/O พอ I/O ทำงานเสร็จสิ้น การแจ้งเตือนจะตกไปอยู่ที่เธรดไหนก็ได้ไม่จำเป็นต้องเป็นเธรดที่สั่ง I/O
  • จัดการกับ Error ที่เกิดขึ้นกับ Device ได้ง่าย เนื่องจากการแจ้งเตือนจะถูกส่งมาหลังจากทำงานเสร็จสิ้นหรือมี Error เกิดขึ้นเท่านั้น

ส่วนข้อเสียคือ

  • เขียนได้ยากกว่าแบบ NoR เนื่องจากต้องมีการจัดการกับ Buffer ที่ยังทำงานไม่เสร็จ
  • พอร์ต Code เก่าๆที่เขียนโดยใช้ NoR ได้ยาก เนื่องจากรูปแบบที่แตกต่างกัน

ใน Unix และ Unix-like ใหม่ๆนั้นส่วนใหญ่จะรองรับ NoC กันหมดแล้ว แต่บาง OS ก็ยังไม่สมบูรณ์ บวกกับแต่ละ OS มี API ที่แตกต่างกัน ทำให้หากต้องการเขียน Code ที่ใช้ NoC บนหลายๆ OS ที่เป็น Unix และ Unix-like นั้นจะยุ่งยากมาก เลยไม่ค่อยเป็นที่นิยม ทำให้ส่วนใหญ่ยังคงใช้แบบ NoR กันอยู่ ส่วนบน Windows NT นั้น I/O ทุกอย่างจะใช้ NoC ทั้งหมด แล้วค่อยทำแบบ Synchronous โดยขี่อยู่บน Asynchronous อีกทีหนึ่ง ซึ่งการรองรับ NoR บน Windows NT นั้นทำขึ้นมาเพียงเพื่อให้ API บางส่วนเกิดความเข้ากันได้กับฝั่ง Unix เท่านั้น

การ Embed Common Language Runtime 4.0 โดยใช้ Visual C++ ภาค 1

สิ่งที่ต้องมี

  • ความรู้ Visual C++
  • ความรู้เกี่ยวกับ Component Object Model (COM)
  • ความรู้เกี่ยวกับ Common Language Runtime (CLR) และเรื่องอื่นๆของ .NET Framework

ภาพรวม

Component สำหรับ Embed CLR นั้น หลักๆจะมีอยู่สามส่วน คือ

  1. ICLRMetaHost เป็น Interface สำหรับจัดการกับ CLR ทั้งหมดที่ติดตั้งอยู่ในเครื่อง เช่น ลิสเวอร์ชั่นที่ติดตั้ง
  2. ICLRRuntimeInfo เป็น Interface สำหรับจัดการกับ CLR เช่น Get พาธของแฟ้มที่ติดตั้ง CLR ตัวนั้น หรือสั่งโหลด CLR เข้ามาใน Process
  3. ICLRRuntimeHost เป็น Interface สำหรับจัดการกับ CLR ที่ถูกโหลดเข้ามาใน Process แล้ว เราจะสั่งรัน Common Intermediate Language (CIL) โดยใช้ Interface นี้

การ Embed นั้น งานหลักๆที่ต้องทำคือ

  1. เรียกฟังชั่น CLRCreateInstance เพื่อสร้าง Object ที่ Implement ICLRMetaHost
  2. ลิส CLR ที่ติดตั้งอยู่ในเครื่องทั้งหมดด้วย ICLRMetaHost::EnumerateInstalledRuntimes แล้วเลือกเวอร์ชั่นที่ต้องการจะ Embed หรือเรียก ICLRMetaHost::GetRuntime เพื่อระบุเวอร์ชั่นที่ต้องการได้เลย
  3. เรียก ICLRRuntimeInfo::GetInterface เพื่อโหลด CLR เวอร์ชั่นที่เลือกเข้ามาใน Process
  4. เริ่มการทำงานของ CLR ด้วยการเรียก ICLRRuntimeHost::Start
  5. รัน CIL ด้วย ICLRRuntimeHost::ExecuteInDefaultAppDomain

รายละเอียด

ก่อนอื่น ให้เรา Initialize COM สำหรับเธรดหลักโดยใช้ CoInitializeEx ก่อน ตัวอย่าง

hrResult = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_SPEED_OVER_MEMORY);

จากนั้น ให้เรียก CLRCreateInstance เพื่อที่จะ Get ข้อมูลของ CLR เวอร์ชั่นที่ต้องการ ตัวอย่าง

ATL::CComPtr pClrMetaHost;

hrResult = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, reinterpret_cast(&pClrMetaHost));

ต่อไปสิ่งที่เราจะทำก็คือ เลือก CLR เวอร์ชั่นที่ต้องการใช้งาน ตัวอย่างการเลือกเวอร์ชั่น 4.0

ATL::CComPtr pClrInfo;

hrResult = pClrMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, reinterpret_cast(&pClrInfo));

พารามิเตอร์แรกของ ICLRMetaHost::GetRuntime จะเป็นเวอร์ชั่นของ CLR ที่ต้องการ โดยชื่อจะต้องตรงกับชื่อแฟ้มที่อยู่ใน C:\Windows\Microsoft.NET\Framework
พอได้ ICLRRuntimeInfo มาแล้ว สิ่งต่อไปที่เราจะทำก็คือ โหลด CLR ตัวนั้นเข้ามาใน Process ตัวอย่าง

ATL::CComPtr pClr;

hrResult = pClrInfo->GetInterface(CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, reinterpret_cast(&pClr));

เมื่อ CLR ถูกโหลดเข้ามาใน Process แล้ว เราต้องเริ่มการทำงานของ CLR ตัวนั้นเสียก่อน ถึงจะสามารถรัน CIL ได้ ตัวอย่างการรัน CLR

hrResult = pClr->Start();

เมื่อทุกอย่างพร้อมแล้ว สิ่งที่เราจะทำต่อไปก็คือ รัน CIL ที่เขียนขึ้นด้วยโค๊ดของ C# ต่อไปนี้

using System.Windows.Forms;

namespace Putta.CLREmbedding
{
    public class Application
    {
        public static int Start(string commandLine)
        {
            return (int)MessageBox.Show(string.Format("Message from CIL World ! Application Command Line is: {0}", commandLine), "Message", MessageBoxButtons.OKCancel);
        }
    }
}

ตัวอย่างการรัน

hrResult = pClr->ExecuteInDefaultAppDomain(L"Application.dll", L"Putta.CLREmbedding.Application", L"Start", pszCommandLine, &dwCilResult);

พารามิเตอร์แรกจะเป็นชื่อ Assembly ของ CIL ที่เราต้องการจะรัน พารามิเตอร์ที่สองจะเป็นชื่อคลาสของ Method ที่จะรัน ส่วนพารามิเตอร์ที่สามจะเป็นชื่อ Method ที่ต้องการจะรัน โดยต้องประกาศเป็นแบบ public static int และรับพารามิเตอร์เป็น string หนึ่งตัวเท่านั้น ส่วนค่าที่ Return จาก Method จะถูกเก็บไว้ใน dwCilResult

Project ตัวอย่าง

ดาวน์โหลดได้จาก ที่นี่

วิธีตรวจจับ Memory Leak โดยง่ายของ Visual C++

วิธีนี้จะตรวจจับได้เฉพาะ Memory ที่ Alloc โดยใช้ฟังชั่นของ CRT (C-Runtime) เท่านั้นนะคับ เช่น malloc, realloc, operator new ฯลฯ และจะต้องเป็น Debug Build เท่านั้น หากเป็น Release Build ตัวตรวจจับจะถูกถอดออกอัตโนมัติ ส่วน Memory ที่ Alloc โดยใช้ฟังชั่นนอกเหนือจากนั้น (เช่น HeapAlloc ของ Win32 API) จะไม่สามารถตรวจจับด้วยวิธีนี้ได้ โดยการตรวจจับจะรายงานผลตอนที่โปรแกรมจบการทำงานแล้วผ่านทาง Debug Message ซึ่งดูได้ด้วยโปรแกรม DebugView หรือช่อง Output ของ Visual Studio หาก Debug ใน Visual Studio

วิธีการคือ ให้เรียกฟังชั่น _CrtSetDbgFlag (ต้อง include crtdbg.h เข้ามาก่อน) แบบตัวอย่างด้านล่างตอนที่โปรแกรมเริ่มการทำงาน

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

ตัวอย่างการใช้งานใน DllMain

#include <crtdbg.h>

BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID /* lpReserved */)
{
	if (DLL_PROCESS_ATTACH == ul_reason_for_call)
	{
		DWORD dwResult, dwBufferSize;
		wchar_t *pszModuleName;

		// Enable C-Runtime memory leak reporter
		_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);

		// Get Module Name
		pszModuleName = nullptr;
		dwBufferSize = MAX_PATH / 2;

		do
		{
			free(pszModuleName);
			pszModuleName = reinterpret_cast<wchar_t *>(malloc((dwBufferSize *= 2) * sizeof(wchar_t)));

			dwResult = GetModuleFileName(hModule, pszModuleName, dwBufferSize);
		} while (dwResult == dwBufferSize);

		PathStripPath(pszModuleName);

		g_pszModuleName = pszModuleName;

		// Init WTL
		_Module.Init(NULL, hModule);
	}
	else if (DLL_PROCESS_DETACH == ul_reason_for_call)
	{
		// Free allocated memory
		free(const_cast<wchar_t *>(g_pszConfigDirectory));
		free(const_cast<wchar_t *>(g_pszNppDirectory));
		free(const_cast<wchar_t *>(g_pszModuleName));

		// Terminate WTL
		_Module.Term();
	}

	return TRUE;
}

ตัวอย่างการรายงานผล

Categories: C, C++, Programming, Software Development Tags:

Performance ระหว่าง i++ และ ++i

คนเก่งหลายๆคน มักจะใช้ ++i แทนการใช้ i++ ใน for ลูป โดยคิดว่า มันทำงานเร็วกว่า i++ ซึ่งก็ไม่แปลกที่จะคิดแบบนั้น เพราะตัวผมเองก็เห็นคำตอบของชาวต่างชาติหลายคนบอกไว้แบบนี้เหมือนกัน แต่ในความเป็นจริงแล้ว บน Visual C++ มันไม่ต่างกันเลย

เมื่อ Visual C++ คอมไพล์ Code นี้

int _tmain(int argc, _TCHAR* argv[])
{
	for (int i = 0; i < argc; ++i)
		_tprintf(_T("%d"), i);

	return 0;
}

Code ที่ได้จะเป็น

	mov	DWORD PTR _i$5259[ebp], 0
	jmp	SHORT $LN3@wmain
$LN2@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	add	eax, 1
	mov	DWORD PTR _i$5259[ebp], eax
$LN3@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	cmp	eax, DWORD PTR _argc$[ebp]
	jge	SHORT $LN1@wmain
; Line 10
	mov	esi, esp
	mov	eax, DWORD PTR _i$5259[ebp]
	push	eax
	push	OFFSET ??_C@_15KNBIKKIN@?$AA?$CF?$AAd?$AA?$AA@
	call	DWORD PTR __imp__wprintf
	add	esp, 8
	cmp	esi, esp
	call	__RTC_CheckEsp
	jmp	SHORT $LN2@wmain

และเมื่อคอมไพล์ Code นี้

int _tmain(int argc, _TCHAR* argv[])
{
	for (int i = 0; i < argc; i++)
		_tprintf(_T("%d"), i);

	return 0;
}

Code ที่ได้จะเป็น

	mov	DWORD PTR _i$5259[ebp], 0
	jmp	SHORT $LN3@wmain
$LN2@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	add	eax, 1
	mov	DWORD PTR _i$5259[ebp], eax
$LN3@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	cmp	eax, DWORD PTR _argc$[ebp]
	jge	SHORT $LN1@wmain
; Line 10
	mov	esi, esp
	mov	eax, DWORD PTR _i$5259[ebp]
	push	eax
	push	OFFSET ??_C@_15KNBIKKIN@?$AA?$CF?$AAd?$AA?$AA@
	call	DWORD PTR __imp__wprintf
	add	esp, 8
	cmp	esi, esp
	call	__RTC_CheckEsp
	jmp	SHORT $LN2@wmain

สังเกตตรง

$LN2@wmain:
	mov	eax, DWORD PTR _i$5259[ebp]
	add	eax, 1
	mov	DWORD PTR _i$5259[ebp], eax

จะเห็นว่ามันเหมือนกันเปะ

Categories: C, C++, Programming, Software Development Tags:

ประโยชน์ของ DateTimeOffset และการใช้งานเบื้องต้น

ปรกติแล้ว ใน .NET Framework เรามักจะใช้ DateTime ในการจัดเก็บเวลากัน ซึ่ง คลาสนี้จะมีข้อเสียอยู่ คือ มันไม่สามารถเก็บ Timezone ไว้ได้ ทำให้เวลามันไม่อิงที่เวลา UTC เพราะมันเก็บแค่เวลาของพื้นที่นั้นๆ ผลที่ตามมาคือ เราจะไม่สามารถแสดงผลและเปรียบเทียบเวลาได้อย่างถูกต้องในบางสถานการณ์

ตัวอย่างสถานการณ์ที่จะเกิดปัญหา

  1. นาย A อยู่ที่ญี่ปุ่น (UTC +09.00)
  2. นาย B อยู่ที่อังกฤษ (UTC +00.00)
  3. นาย B ส่งข้อความไปให้นาย A โดยข้อมูลที่ถูกส่งไปมีสองอย่างคือ ข้อความ และ เวลาที่ข้อความถูกส่งออกมา สมมุติให้เวลาขณะนั้นของนาย B เป็น 07:00 นาฬิกา และเวลาของนาย A จะเท่ากับ 16:00 นาฬิกา (เอาเวลาของนาย B บวกด้วย 09:00 ซึ่งเป็น Timezone ของนาย A จะได้เวลาปัจจุบันของนาย A สาเหตุที่บวก 09:00 ตรงๆเพราะว่า Timezone ของนาย B เป็น +00:00)
  4. หากใช้ DateTime เก็บเวลา นาย A จะเห็นว่าข้อความมันถูกส่งมาตอน 07.00 นาฬิกาของญี่ปุ่น (ซึ่งขณะนั้นญี่ปุ่นเป็นเวลา 16:00 นาฬิกา) ทั้งๆที่มันพึ่งถูกส่งออกมา !

ปัญหานี้แก้ได้โดยการใช้ DateTimeOffset เมื่อนาย B ส่งข้อความไปให้นาย A พร้อมด้วยเวลาที่ข้อความถูกส่งออกมาโดยใช้ DateTimeOffset เก็บเวลา เมื่อนาย A ได้รับข้อความ จะรู้ทันทีว่านาย B อยู่ Timezone ไหน และจะสามารถปรับการแสดงผลของเวลาได้ถูกต้อง เพราะเวลาที่เก็บใน DateTimeOffset เป็นเวลา UTC +00.00 เสมอ (แต่เวลาแสดงผลจริงมันจะบวก Timezone ให้เราด้วย) ไม่ว่าโปรแกรมจะทำงานอยู่ที่ Timezone ไหนก็ตาม

สิ่งน่ารู้เกี่ยวกับ DateTimeOffset

  • เวลาเปรียบเทียบ มันจะเปรียบเทียบโดยใช้เวลาที่เก็บอยู่อย่างเดียว (ซึ่งก็คือเวลา UTC +00:00) โดยไม่เปรียบเทียบ Timezone ตัวอย่าง
    using System;
    
    namespace TestDateTimeOffset
    {
        class Program
        {
            static void Main(string[] args)
            {
                DateTimeOffset thailand, japan;
    
                thailand = DateTimeOffset.Now;
                japan = thailand.ToOffset(TimeSpan.FromHours(9));
    
                Console.WriteLine("Thailand Time: {0}", thailand);
                Console.WriteLine("   Japan Time: {0}", japan);
    
                if (thailand == japan)
                    Console.WriteLine("This line will be printed.");
            }
        }
    }
    

    ผล

  • เวลาที่ได้จาก DateTimeOffset.Now จะเป็นเวลา UTC +00:00 เสมอ แต่ Timezone ที่ได้จะเป็น Timezone ของเครื่องที่โปรแกรมทำงานอยู่ ทำให้การเปรียบเทียบและการแสดงผลของเวลาที่ได้ถูกต้องตลอด

Compiler และ Linker ของภาษา C สร้างโปรแกรมหนึ่งตัวออกมาได้อย่างไร

2012/02/22 1 comment

ในภาษา C นั้น จะใช้ไฟล์สองชนิด คือ Source File (ที่นามสกุล .c) และ Header File (นามสกุล .h) ในการเก็บ Source Code ซึ่งแตกต่างจากภาษายุคใหม่หลายๆภาษาที่ใช้เพียงไฟล์ชนิดเดียวในการเก็บ Source Code

ระบบการคอมไพล์ของภาษา C นั้น ตัวคอมไพล์เลอร์จะแปลง Source File แต่ละไฟล์ในโปรเจคออกมาเป็น Object File ให้หมดก่อน (หนึ่ง Source File จะได้หนึ่ง Object File) แต่ละไฟล์จะถูกคอมไพล์แบบตัวใครตัวมัน คือ ไม่เกี่ยวข้องกับ Source File ตัวอื่นๆ จากนั้น Linker จะทำการรวม Object File ทั้งหมดเข้าด้วยกันเพื่อสร้าง Executable ที่พร้อมใช้งานขึ้นมา (หรือ Shared Library)

แล้วไอ้ Linker นี่ทำไมต้องมีมันด้วย ทำไมไม่มีแค่คอมไพล์เลอร์ตัวเดียวให้มันจบๆไป? สาเหตุที่ต้องมี Linker ก็เพราะว่า การเขียนโปรเจคใหญ่ๆใน Source File อันเดียวเป็นอะไรที่ไม่ควรทำอย่างยิ่ง ลองนึกสภาพ Code ของระบบปฏิบัติการที่มีเป็นล้านๆบรรทัดถูกเก็บอยู่ใน Source File อันเดียวดู

เมื่อมีการแยก Source File การ Forward declaration ก็ตามมาเพื่อให้ Source File สามารถเรียกใช้งานฟังชั่นที่อยู่ใน Source File อันอื่นได้ การ Forward declaration คือ การประกาศชื่อฟังชั่นล่วงหน้าเพื่อให้คอมไพล์เลอร์รู้ว่าควรจะเรียกฟังชั่นนั้นอย่างไร และฟังชั่นนั้นคืนค่าชนิดไหนกลับมา ซึ่ง Linker จะเป็นตัวจัดการฟังชั่นตัวจริงที่อยู่ใน Source File อันอื่นเอง

อ้าว แล้ว Header File มันมีประโยชน์อย่างไรละ ในเมื่อมันคอมไพล์แค่ Source File อย่างเดียว? สำหรับประโยชน์ของ Header File นั้นจะมีไว้ให้ Source File ใช้ Include เข้าไป เพื่อแยก Code ออกเป็นส่วนๆ เช่น ส่วนที่สามารถใช้งานได้ในหลาย Source File ก็จะถูกแยกออกมาใส่ใน Header File

การ Include นั้น คอมไพล์เลอร์จะใช้การอ่าน Code ในไฟล์ที่ต้องการ Include เข้ามาใส่ใน Source File โดยตรง ไม่มีอะไรลึกลับซับซ้อนนอกเหนือจากนั้น ตัวอย่างเช่น ให้ไฟล์ foo.h มี Code ดังนี้

#ifndef _FOO_H_
#define _FOO_H_

int bar();

#endif // _FOO_H_

และไฟล์ foo.c มี Code ดังนี้

#include "foo.h"

int main(int argc, char *argv[])
{
	return bar();
}

เมื่อคอมไพล์เลอร์ทำการคอมไพล์ไฟล์ foo.c ตัวคอมไพล์เลอร์จะทำการประมวลผล Preprocessor ก่อนการคอมไพล์ Code (พวกที่ขึ้นต้นด้วย # นั่นละ เช่น #include) แล้ว Code ของไฟล์ foo.c ที่คอมไพล์เลอร์เข้าใจจะกลายเป็น

#ifndef _FOO_H_
#define _FOO_H_

int bar();

#endif // _FOO_H_

int main(int argc, char *argv[])
{
	return bar();
}

ไม่งงใช่มั้ย? ส่วนปัญหาที่เหลือๆก็ลองคิดกันดู เช่น ทำไมใน Header File ส่วนมากจะมีไว้ทำ Forward declaration และทำไมใน Header File มันถึงมี #ifdef กับ #define อะไรแปลกๆนั้นบนหัวไฟล์ตลอด และทำไมตัวแปรที่ประกาศใน Header File จะต้องใช้ extern นำหน้าทุกครั้ง ฯลฯ

อาจจะอ่านได้งงไปนิด เพราะผมตั้งใจให้อ่านไปแล้วคิดเองตามไปด้วย ส่วนภาษา C++ ก็ไม่ต่างกัน