Archive

Archive for the ‘C’ Category

วิธีตรวจจับ 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;
}

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

Advertisements
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:

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++ ก็ไม่ต่างกัน