摘自:http://www.diybl.com/course/3_program/c++/cppjs/2008426/111619.html
去年11月的MSDN雜志曾刊登過(guò)一篇文章 Break Free of Code Deadlocks in Critical Sections Under Windows ,Matt Pietrek 和 Russ Osterlund 兩位對(duì)臨界區(qū)(Critical Section)的內(nèi)部實(shí)現(xiàn)做了一次簡(jiǎn)短的介紹,但點(diǎn)到為止,沒(méi)有繼續(xù)深入下去,當(dāng)時(shí)給我的感覺(jué)就是癢癢的,呵呵,于是用IDA和SoftIce大致分析了一下臨界區(qū)的實(shí)現(xiàn),大致弄明白了原理后也就沒(méi)有深究。現(xiàn)在乘著Win2k源碼的東風(fēng),重新分析一下這塊的內(nèi)容,做個(gè)小小的總結(jié)吧 :P
臨界區(qū)(Critical Section)是Win32中提供的一種輕量級(jí)的同步機(jī)制,與互斥(Mutex)和事件(Event)等內(nèi)核同步對(duì)象相比,臨界區(qū)是完全在用戶(hù)態(tài)維護(hù)的,所以?xún)H能在同一進(jìn)程內(nèi)供線程同步使用,但也因此無(wú)需在使用時(shí)進(jìn)行用戶(hù)態(tài)和核心態(tài)之間的切換,工作效率大大高于其它同步機(jī)制。
臨界區(qū)的使用方法非常簡(jiǎn)單,使用 InitializeCriticalSection 或 InitializeCriticalSectionAndSpinCount 函數(shù)初始化一個(gè) CRITICAL_SECTION 結(jié)構(gòu);使用 SetCriticalSectionSpinCount 函數(shù)設(shè)置臨界區(qū)的Spin計(jì)數(shù)器;然后使用 EnterCriticalSection 或 TryEnterCriticalSection 獲取臨界區(qū)的所有權(quán);完成需要同步的操作后,使用 LeaveCriticalSection 函數(shù)釋放臨界區(qū);最后使用 DeleteCriticalSection 函數(shù)析構(gòu)臨界區(qū)結(jié)構(gòu)。
以下是MSDN中提供的一個(gè)簡(jiǎn)單的例子:
以下為引用:
// Global variable
CRITICAL_SECTION CriticalSection;
void main()
{
...
// Initialize the critical section one time only.
if (!InitializeCriticalSectionAndSpinCount(&CriticalSection, 0x80000400) )
return;
...
// Release resources used by the critical section object.
DeleteCriticalSection(&CriticalSection)
}
DWORD WINAPI ThreadProc( LPVOID lpParameter )
{
...
// Request ownership of the critical section.
EnterCriticalSection(&CriticalSection);
// Access the shared resource.
// Release ownership of the critical section.
LeaveCriticalSection(&CriticalSection);
...
}
首先看看構(gòu)造和析構(gòu)臨界區(qū)結(jié)構(gòu)的函數(shù)。
InitializeCriticalSection 函數(shù)(ntosdll esource.c:1210)實(shí)際上是調(diào)用 InitializeCriticalSectionAndSpinCount 函數(shù)(resource.c:1266)完成功能的,只不過(guò)傳入一個(gè)值為0的初始Spin計(jì)數(shù)器;InitializeCriticalSectionAndSpinCount 函數(shù)主要完成兩部分工作:初始化 RTL_CRITICAL_SECTION 結(jié)構(gòu)和 RTL_CRITICAL_SECTION_DEBUG 結(jié)構(gòu)。前者是臨界區(qū)的核心結(jié)構(gòu),下面將著重討論;后者是調(diào)試用結(jié)構(gòu),Matt 那篇文章里面分析的很清楚了,我這兒就不羅嗦了 :P
RTL_CRITICAL_SECTION結(jié)構(gòu)在winnt.h中定義如下:
以下為引用:
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
//
// The following three fields control entering and exiting the critical
// section for the resource
//
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread; // from the thread''s
ClientId->UniqueThread
HANDLE LockSemaphore;
ULONG_PTR SpinCount; // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
InitializeCriticalSectionAndSpinCount 函數(shù)中首先對(duì)臨界區(qū)結(jié)構(gòu)進(jìn)行了初始化
DebugInfo 字段指向初始化臨界區(qū)時(shí)分配的RTL_CRITICAL_SECTION_DEBUG結(jié)構(gòu);
LockCount 字段是臨界區(qū)中最重要的字段,初始值為-1,當(dāng)臨界區(qū)被獲取(Hold)時(shí)此字段大于等于0;
RecursionCount 字段保存當(dāng)前臨界區(qū)所有者線程的獲取緩沖區(qū)嵌套層數(shù),初始值為0;
OwningThread 字段保存當(dāng)前臨界區(qū)所有者線程的句柄,初始值為0;
LockSemaphore 字段實(shí)際上是一個(gè)auto-reset的事件句柄,用于喚醒等待獲取臨界區(qū)的阻塞線程,初始值為0;
SpinCount 字段用于在多處理器環(huán)境下完成輕量級(jí)的CPU見(jiàn)同步,單處理器時(shí)沒(méi)有使用(初始值為0),多處理器時(shí)設(shè)置為SpinCount參數(shù)值(最大為MAX_SPIN_COUNT=0x00ffffff)。此外 RtlSetCriticalSectionSpinCount 函數(shù)(resource.c:1374)的代碼與這兒設(shè)置SpinCount的代碼完全一樣。
初始化臨界區(qū)結(jié)構(gòu)后,函數(shù)會(huì)根據(jù)SpinCount參數(shù)的一個(gè)標(biāo)志位判斷是否需要預(yù)先初始化 LockSemaphore 字段,如果需要?jiǎng)t使用NtCreateEvent創(chuàng)建一個(gè)具有訪問(wèn)權(quán)限的同步用事件核心對(duì)象,初始狀態(tài)為沒(méi)有激發(fā)。這一初始化本來(lái)是在 EnterCriticalSection 函數(shù)中完成的,將之顯式提前可以進(jìn)一步優(yōu)化 EnterCriticalSection 函數(shù)的性能。
值得注意的是,這一特性?xún)H對(duì)Win2k有效。MSDN里面說(shuō)明如下:
以下為引用:
Windows 2000: If the high-order bit is set, the function preallocates the event used by the EnterCriticalSection function. Do not set this bit if you are creating a large number of critical section objects, because it will consume a significant amount of nonpaged pool. This flag is not necessary on Windows XP and later, and it is ignored.
與之對(duì)應(yīng)的 DeleteCriticalSection 函數(shù)完成關(guān)閉事件句柄和是否調(diào)試結(jié)構(gòu)的功能。
臨界區(qū)真正的核心代碼在win2kprivate tosdlli386critsect.asm里面,包括_RtlEnterCriticalSection、_RtlTryEnterCriticalSection和_RtlLeaveCriticalSection三個(gè)函數(shù)。
_RtlEnterCriticalSection 函數(shù) (critsect.asm:85) 首先檢查SpinCount是否為0,如果不為0則處理多處理器架構(gòu)下的問(wèn)題[分支1];如為0則使用原子操作給LockCount加一,并判斷是否其值為0。如果加一后LockCount大于0,則此臨界區(qū)已經(jīng)被獲取[分支2];如為0則獲取當(dāng)前線程TEB中的線程句柄,保存在臨界區(qū)的OwningThread字段中,并將RecursionCount設(shè)置為1。調(diào)試版本則調(diào)用RtlpCriticalSectionIsOwned函數(shù)在調(diào)試模式下驗(yàn)證此緩沖區(qū)是被當(dāng)前線程獲取的,否則在調(diào)試模式下激活調(diào)試器。最后還會(huì)更新TEB的CountOfOwnedCriticalSections計(jì)數(shù)器,以及臨界區(qū)調(diào)試結(jié)構(gòu)中的EntryCount字段。
如果此臨界區(qū)已經(jīng)被獲取[分支2],則判斷獲取臨界區(qū)的線程句柄是否與當(dāng)前線程相符。如果是同一線程則直接將RecursionCount和調(diào)試結(jié)構(gòu)的EntryCount字段加一;如果不是當(dāng)前線程,則調(diào)用RtlpWaitForCriticalSection函數(shù)等待此臨界區(qū),并從頭開(kāi)始執(zhí)行獲取臨界區(qū)的程序。
多CPU情況的分支處理方式類(lèi)似,只是多了對(duì)SpinCount的雙重檢查處理。
接著的_RtlTryEnterCriticalSection(critsect.asm:343)函數(shù)是一個(gè)輕量級(jí)的嘗試獲取臨界區(qū)的函數(shù)。偽代碼如下:
以下為引用:
if(CriticalSection->LockCount == -1)
{
// 臨界區(qū)可用
CriticalSection->LockCount = 0;
CriticalSection->OwningThread = TEB->ClientID;
CriticalSection->RecursionCount = 1;
return TRUE;
}
else
{
if(CriticalSection->OwningThread == TEB->ClientID)
{
// 臨界區(qū)是當(dāng)前線程獲取
CriticalSection->LockCount++;
CriticalSection->RecursionCount++;
return TRUE;
}
else
{
// 臨界區(qū)已被其它線程獲取
return FALSE;
}
}
最后的_RtlLeaveCriticalSection(critsect.asm:271)函數(shù)釋放已獲取的臨界區(qū),實(shí)現(xiàn)就比較簡(jiǎn)單了,實(shí)際上就是對(duì)嵌套計(jì)數(shù)器和鎖定計(jì)數(shù)器進(jìn)行操作。偽代碼如下:
以下為引用:
if(--CriticalSection->RecursionCount == 0)
{
bsp; // 臨界區(qū)已不再被使用
CriticalSection->OwningThread = 0;
if(--CriticalSection->LockCount)
{
// 仍有線程鎖定在臨界區(qū)上
_RtlpUnWaitCriticalSection(CriticalSection)
}
}
else
{
--CriticalSection->LockCount
}
一、宏中“#”和“##”的用法:
一般用法:使用“#”把宏參數(shù)變?yōu)橐粋€(gè)字符串,用”##”把兩個(gè)宏參數(shù)結(jié)合在一起
例子:
#include <iostream>
using namespace std;

#define TEST1(x) (cout<<id##x<<endl);
#define TEST2(p) (cout<<#p<<endl);
int main()


{
int id1 = 1001;
int id2 = 1002;
TEST1(1); // == cout<< id1 << endl;
TEST2(2); // == cout<< "2" << endl;
TEST1(2); // == cout<< id2 << endl;

system("pause");
return 0;
}
二、防止一個(gè)頭文件被重復(fù)包含
#ifndef COMDEF_H
#define COMDEF_H //頭文件內(nèi)容
#endif
當(dāng)你所建的工程有多個(gè)源文件組成時(shí),很可能會(huì)在多個(gè)文件里頭包含了同一個(gè)頭文件,如果借用上面的宏定義就能夠避免同一個(gè)頭文件被重復(fù)包含時(shí)進(jìn)行多次編譯。因?yàn)楫?dāng)它編譯第一個(gè)頭文件時(shí)總是沒(méi)有定義#define COMDEF_H,那么它將編譯一遍頭文件中所有的內(nèi)容,包括定義#define COMDEF_H。這樣編譯再往下進(jìn)行時(shí)如果遇到同樣要編譯的頭文件,那么由于語(yǔ)句#ifndef COMDEF_H的存在它將不再重復(fù)的編譯這個(gè)頭文件。
三、常用的宏定義
__DATE__
進(jìn)行預(yù)處理的日期(“Mmm dd yyyy”形式的字符串文字)
__FILE__
代表當(dāng)前源代碼文件名的字符串文字
__LINE__
代表當(dāng)前源代碼中的行號(hào)的整數(shù)常量
__TIME__
源文件編譯時(shí)間,格式微“hh:mm:ss”
參考文章:
C中的預(yù)編譯宏定義 http://blog.readnovel.com/article/htm/tid_900939.html
C標(biāo)準(zhǔn)中一些預(yù)定義的宏 http://www.programfan.com/article/2883.html
C語(yǔ)言常用宏定義技巧 http://blog.21ic.com/user1/3074/archives/2008/51567.html
C語(yǔ)言宏定義技巧(常用宏定義) http://blog.21ic.com/user1/69/archives/2006/13695.html
宏定義:http://blog.csdn.net/believefym/archive/2007/10/21/1836162.aspx
好好學(xué)習(xí)!
好文章,大家一起分享。
一直對(duì)extern "c"不是很明白。今天,看了一篇文章《C++中extern “C”含義深層探索》,對(duì)這個(gè)問(wèn)題終于有所認(rèn)識(shí)。轉(zhuǎn)過(guò)來(lái)與大家分享。
鏈接:
http://developer.51cto.com/art/200510/9066.htm作者:宋寶華
1.引言
C++語(yǔ)言的創(chuàng)建初衷是“a better C”,但是這并不意味著C++中類(lèi)似C語(yǔ)言的全局變量和函數(shù)所采用的編譯和連接方式與C語(yǔ)言完全相同。作為一種欲與C兼容的語(yǔ)言,C++保留了一部分過(guò)程式語(yǔ)言的特點(diǎn)(被世人稱(chēng)為“不徹底地面向?qū)ο?#8221;),因而它可以定義不屬于任何類(lèi)的全局變量和函數(shù)。但是,C++畢竟是一種面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言,為了支持函數(shù)的重載,C++對(duì)全局函數(shù)的處理方式與C有明顯的不同。
2.從標(biāo)準(zhǔn)頭文件說(shuō)起
某企業(yè)曾經(jīng)給出如下的一道面試題:
面試題:為什么標(biāo)準(zhǔn)頭文件都有類(lèi)似以下的結(jié)構(gòu)?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
分析
顯然,頭文件中的編譯宏“#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif” 的作用是防止該頭文件被重復(fù)引用。
那么
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
的作用又是什么呢?我們將在下文一一道來(lái)。
3.深層揭密extern "C"
extern "C" 包含雙重含義,從字面上即可得到:首先,被它修飾的目標(biāo)是“extern”的;其次,被它修飾的目標(biāo)是“C”的。讓我們來(lái)詳細(xì)解讀這兩重含義。
被extern "C"限定的函數(shù)或變量是extern類(lèi)型的;
extern是C/C++語(yǔ)言中表明函數(shù)和全局變量作用范圍(可見(jiàn)性)的關(guān)鍵字,該關(guān)鍵字告訴編譯器,其聲明的函數(shù)和變量可以在本模塊或其它模塊中使用。記住,下列語(yǔ)句:
extern int a;
僅僅是一個(gè)變量的聲明,其并不是在定義變量a,并未為a分配內(nèi)存空間。變量a在所有模塊中作為一種全局變量只能被定義一次,否則會(huì)出現(xiàn)連接錯(cuò)誤。
通常,在模塊的頭文件中對(duì)本模塊提供給其它模塊引用的函數(shù)和全局變量以關(guān)鍵字extern聲明。例如,如果模塊B欲引用該模塊A中定義的全局變量和函數(shù)時(shí)只需包含模塊A的頭文件即可。這樣,模塊B中調(diào)用模塊A中的函數(shù)時(shí),在編譯階段,模塊B雖然找不到該函數(shù),但是并不會(huì)報(bào)錯(cuò);它會(huì)在連接階段中從模塊A編譯生成的目標(biāo)代碼中找到此函數(shù)。
與extern對(duì)應(yīng)的關(guān)鍵字是static,被它修飾的全局變量和函數(shù)只能在本模塊中使用。因此,一個(gè)函數(shù)或變量只可能被本模塊使用時(shí),其不可能被extern “C”修飾。
被extern "C"修飾的變量和函數(shù)是按照C語(yǔ)言方式編譯和連接的;
未加extern “C”聲明時(shí)的編譯方式
首先看看C++中對(duì)類(lèi)似C的函數(shù)是怎樣編譯的。
作為一種面向?qū)ο蟮恼Z(yǔ)言,C++支持函數(shù)重載,而過(guò)程式語(yǔ)言C則不支持。函數(shù)被C++編譯后在符號(hào)庫(kù)中的名字與C語(yǔ)言的不同。例如,假設(shè)某個(gè)函數(shù)的原型為:
void foo( int x, int y );
該函數(shù)被C編譯器編譯后在符號(hào)庫(kù)中的名字為_(kāi)foo,而C++編譯器則會(huì)產(chǎn)生像_foo_int_int之類(lèi)的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機(jī)制,生成的新名字稱(chēng)為“mangled name”)。
foo_int_int這樣的名字包含了函數(shù)名、函數(shù)參數(shù)數(shù)量及類(lèi)型信息,C++就是靠這種機(jī)制來(lái)實(shí)現(xiàn)函數(shù)重載的。例如,在C++中,函數(shù)void foo( int x, int y )與void foo( int x, float y )編譯生成的符號(hào)是不相同的,后者為_(kāi)foo_int_float。
同樣地,C++中的變量除支持局部變量外,還支持類(lèi)成員變量和全局變量。用戶(hù)所編寫(xiě)程序的類(lèi)成員變量可能與全局變量同名,我們以"."來(lái)區(qū)分。而本質(zhì)上,編譯器在進(jìn)行編譯時(shí),與函數(shù)的處理相似,也為類(lèi)中的變量取了一個(gè)獨(dú)一無(wú)二的名字,這個(gè)名字與用戶(hù)程序中同名的全局變量名字不同。
未加extern "C"聲明時(shí)的連接方式
假設(shè)在C++中,模塊A的頭文件如下:
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
在模塊B中引用該函數(shù):
// 模塊B實(shí)現(xiàn)文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);
實(shí)際上,在連接階段,連接器會(huì)從模塊A生成的目標(biāo)文件moduleA.obj中尋找_foo_int_int這樣的符號(hào)!
加extern "C"聲明后的編譯和連接方式
加extern "C"聲明后,模塊A的頭文件變?yōu)椋?br>// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
在模塊B的實(shí)現(xiàn)文件中仍然調(diào)用foo( 2,3 ),其結(jié)果是:
(1)模塊A編譯生成foo的目標(biāo)代碼時(shí),沒(méi)有對(duì)其名字進(jìn)行特殊處理,采用了C語(yǔ)言的方式;
(2)連接器在為模塊B的目標(biāo)代碼尋找foo(2,3)調(diào)用時(shí),尋找的是未經(jīng)修改的符號(hào)名_foo。
如果在模塊A中函數(shù)聲明了foo為extern "C"類(lèi)型,而模塊B中包含的是extern int foo( int x, int y ) ,則模塊B找不到模塊A中的函數(shù);反之亦然。
所以,可以用一句話概括extern “C”這個(gè)聲明的真實(shí)目的(任何語(yǔ)言中的任何語(yǔ)法特性的誕生都不是隨意而為的,來(lái)源于真實(shí)世界的需求驅(qū)動(dòng)。我們?cè)谒伎紗?wèn)題時(shí),不能只停留在這個(gè)語(yǔ)言是怎么做的,還要問(wèn)一問(wèn)它為什么要這么做,動(dòng)機(jī)是什么,這樣我們可以更深入地理解許多問(wèn)題):
實(shí)現(xiàn)C++與C及其它語(yǔ)言的混合編程。
明白了C++中extern "C"的設(shè)立動(dòng)機(jī),我們下面來(lái)具體分析extern "C"通常的使用技巧。
4.extern "C"的慣用法
(1)在C++中引用C語(yǔ)言中的函數(shù)和變量,在包含C語(yǔ)言頭文件(假設(shè)為cExample.h)時(shí),需進(jìn)行下列處理:
extern "C"
{
#include "cExample.h"
}
而在C語(yǔ)言的頭文件中,對(duì)其外部函數(shù)只能指定為extern類(lèi)型,C語(yǔ)言中不支持extern "C"聲明,在.c文件中包含了extern "C"時(shí)會(huì)出現(xiàn)編譯語(yǔ)法錯(cuò)誤。
筆者編寫(xiě)的C++引用C函數(shù)例子工程中包含的三個(gè)文件的源代碼如下:
/* c語(yǔ)言頭文件:cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c語(yǔ)言實(shí)現(xiàn)文件:cExample.c */
#include "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++實(shí)現(xiàn)文件,調(diào)用add:cppFile.cpp
extern "C"
{
#include "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++調(diào)用一個(gè)C語(yǔ)言編寫(xiě)的.DLL時(shí),當(dāng)包括.DLL的頭文件或聲明接口函數(shù)時(shí),應(yīng)加extern "C" { }。
(2)在C中引用C++語(yǔ)言中的函數(shù)和變量時(shí),C++的頭文件需添加extern "C",但是在C語(yǔ)言中不能直接引用聲明了extern "C"的該頭文件,應(yīng)該僅將C文件中將C++中定義的extern "C"函數(shù)聲明為extern類(lèi)型。
筆者編寫(xiě)的C引用C++函數(shù)例子工程中包含的三個(gè)文件的源代碼如下:
//C++頭文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++實(shí)現(xiàn)文件 cppExample.cpp
#include "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C實(shí)現(xiàn)文件 cFile.c
/* 這樣會(huì)編譯出錯(cuò):#include "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
如果深入理解了第3節(jié)中所闡述的extern "C"在編譯和連接階段發(fā)揮的作用,就能真正理解本節(jié)所闡述的從C++引用C函數(shù)和C引用C++函數(shù)的慣用法。
MUI,其英文全拼為:Multilingual User Interface。這幾天思考如何運(yùn)用多語(yǔ)言的問(wèn)題,查到了一些相關(guān)資料,貼出來(lái)與大家分享。
Multilingual User Interface (MUI)
http://msdn.microsoft.com/en-us/library/aa913592.aspx
The Multilingual User Interface (MUI) allows users to change the language of the user interface (UI). To make this possible, the MUI uses a single core binary that includes the system default language, together with one resource dynamic-link library (DLL) for each additional target language. The target device boots with the system default language and then a new user-selected language goes into effect after a soft reset. This switch requires recreating windows, menus, and dialog boxes with the newly loaded resources. In addition, to be considered successful, the language switch must display these elements with the correct fonts and with the correct locale-specific information.
In This Section
For All Platforms:
Multilingual User Interface (MUI) Application Development
Provides an overview of the MUI architecture. Provides instructions on how to work with fonts in a multilingual user-interface environment and shows how to use MUI with applications.
Multilingual User Interface (MUI) Reference
Provides reference pages for the MUI application programming interface.
For Windows Embedded CE:
Multilingual User Interface (MUI) OS Design Development
Provides information about the MUI support that is helpful when designing and developing a Windows Embedded CE OS. This includes dependency information, the modules and components that implement MUI, and MUI implementation considerations.
How to Create a Multilingual Run-time Image Using MUI
Demonstrates how to create a run-time image in both French and English by using MUI.
Multilingual User Interface (MUI) Registry Settings
Provides registry-related information for MUI.
Multilingual User Interface (MUI) Migration
Provides information about factors to consider when migrating to a newer version of Windows Embedded CE
總在快樂(lè)的時(shí)候,感到微微的惶恐
在開(kāi)懷大笑時(shí),留下感動(dòng)的淚水
我無(wú)法相信單純的幸福
對(duì)人生的起伏悲苦,既坦然又不安
————摘自幾米漫畫(huà)
摘了這么幾句與大家分享。心情煩惱得時(shí)候,會(huì)看看漫畫(huà),緩解一些心情。如果你也有心煩意亂的時(shí)候,看看漫畫(huà),或者看看喜劇,開(kāi)懷的一笑,讓你從你煩惱中脫離。
最近心情也相當(dāng)壓抑,甚至萬(wàn)分郁悶。周末了,看到這么幾句,心有所感,與大家分享。
如何添加代碼注釋?zhuān)?br>這個(gè)我一直沒(méi)有搞懂。自己隨便涂鴉,也懶得加注釋。但是真正參加項(xiàng)目,有時(shí)也會(huì)找理由不添注釋。看了一些書(shū),參加一些培訓(xùn),有的人說(shuō),加注釋好,有人說(shuō)加注釋不好。好有好的地方,壞也有壞的味道。對(duì)于我這個(gè)初來(lái)乍到之人來(lái)說(shuō),反而倒是非常迷惑。
最近看到一篇文章“添加代碼注釋13個(gè)技巧”,也解除了心中的部分疑團(tuán)。貼在這里與大家分享,也備以后參考。
鏈接:
http://sunxinhe.yo2.cn/articles/%E3%80%90%E8%BD%AC%E3%80%91%E6%B7%BB%E5%8A%A0%E4%BB%A3%E7%A0%81%E6%B3%A8%E9%87%8A13%E4%B8%AA%E6%8A%80%E5%B7%A7.html添加代碼注釋13個(gè)技巧
作者:José M. Aguilar(西班牙語(yǔ))
英文譯者:Timm Martin
中文譯者:numenzq
下面的13個(gè)技巧向你展示如何添加代碼注釋?zhuān)@些技巧都很容易理解和記憶。
1. 逐層注釋
為每個(gè)代碼塊添加注釋?zhuān)⒃诿恳粚邮褂媒y(tǒng)一的注釋方法和風(fēng)格。例如:
針對(duì)每個(gè)類(lèi):包括摘要信息、作者信息、以及最近修改日期等
針對(duì)每個(gè)方法:包括用途、功能、參數(shù)和返回值等
在團(tuán)隊(duì)工作中,采用標(biāo)準(zhǔn)化的注釋尤為重要。當(dāng)然,使用注釋規(guī)范和工具(例如C#里的XML,Java里的Javadoc)可以更好的推動(dòng)注釋工作完成得更好。
2. 使用分段注釋
如果有多個(gè)代碼塊,而每個(gè)代碼塊完成一個(gè)單一任務(wù),則在每個(gè)代碼塊前添加一個(gè)注釋來(lái)向讀者說(shuō)明這段代碼的功能。例子如下:
// Check that all data records
// are correct
foreach (Record record in records)
{
if (rec.checkStatus()==Status.OK)
{
. . .
}
}
// Now we begin to perform
// transactions
Context ctx = new ApplicationContext();
ctx.BeginTransaction();
. . .
3. 在代碼行后添加注釋
如果多行代碼的每行都要添加注釋?zhuān)瑒t在每行代碼后添加該行的注釋?zhuān)@將很容易理解。例如:
const MAX_ITEMS = 10; // maximum number of packets
const MASK = 0x1F; // mask bit TCP
在分隔代碼和注釋時(shí),有的開(kāi)發(fā)者使用tab鍵,而另一些則使用空格鍵。然而由于tab鍵在各編輯器和IDE工具之間的表現(xiàn)不一致,因此最好的方法還是使用空格鍵。
4. 不要侮辱讀者的智慧
避免以下顯而易見(jiàn)的注釋?zhuān)?/font>
if (a == 5) // if a equals 5
counter = 0; // set the counter to zero
寫(xiě)這些無(wú)用的注釋會(huì)浪費(fèi)你的時(shí)間,并將轉(zhuǎn)移讀者對(duì)該代碼細(xì)節(jié)的理解。
5. 禮貌點(diǎn)
避免粗魯?shù)淖⑨專(zhuān)纾?#8220;注意,愚蠢的使用者才會(huì)輸入一個(gè)負(fù)數(shù)”或“剛修復(fù)的這個(gè)問(wèn)題出于最初的無(wú)能開(kāi)發(fā)者之手”。這樣的注釋能夠反映到它的作者是多么的拙劣,你也永遠(yuǎn)不知道誰(shuí)將會(huì)閱讀這些注釋?zhuān)赡苁牵耗愕睦习澹蛻?hù),或者是你剛才侮辱過(guò)的無(wú)能開(kāi)發(fā)者。
6. 關(guān)注要點(diǎn)
不要寫(xiě)過(guò)多的需要轉(zhuǎn)意且不易理解的注釋。避免ASCII藝術(shù),搞笑,詩(shī)情畫(huà)意,hyperverbosity的注釋。簡(jiǎn)而言之,保持注釋簡(jiǎn)單直接。
7. 使用一致的注釋風(fēng)格
一些人堅(jiān)信注釋?xiě)?yīng)該寫(xiě)到能被非編程者理解的程度。而其他的人則認(rèn)為注釋只要能 被開(kāi)發(fā)人員理解就行了。無(wú)論如何,Successful Strategies for Commenting Code已經(jīng)規(guī)定和闡述了注釋的一致性和針對(duì)的讀者。就個(gè)人而言,我懷疑大部分非編程人員將會(huì)去閱讀代碼,因此注釋?xiě)?yīng)該是針對(duì)其他的開(kāi)發(fā)者而言。
8. 使用特有的標(biāo)簽
在一個(gè)團(tuán)隊(duì)工作中工作時(shí),為了便于與其它程序員溝通,應(yīng)該采用一致的標(biāo)簽集進(jìn)行注釋。例如,在很多團(tuán)隊(duì)中用TODO標(biāo)簽表示該代碼段還需要額外的工作。
int Estimate(int x, int y)
{
// TODO: implement the calculations
return 0;
}
注釋標(biāo)簽切忌不要用于解釋代碼,它只是引起注意或傳遞信息。如果你使用這個(gè)技巧,記得追蹤并確認(rèn)這些信息所表示的是什么。
9. 在代碼時(shí)添加注釋
在寫(xiě)代碼時(shí)就添加注釋?zhuān)@時(shí)在你腦海里的是清晰完整的思路。如果在代碼最后再添 加同樣注釋?zhuān)鼘⒍嗷ㄙM(fèi)你一倍的時(shí)間。而“我沒(méi)有時(shí)間寫(xiě)注釋”,“我很忙”和“項(xiàng)目已經(jīng)延期了”這都是不愿寫(xiě)注釋而找的借口。一些開(kāi)發(fā)者覺(jué)得應(yīng)該 write comments before code,用于理清頭緒。例如:
public void ProcessOrder()
{
// Make sure the products are available
// Check that the customer is valid
// Send the order to the store
// Generate bill
}
10. 為自己注釋代碼
當(dāng)注釋代碼時(shí),要考慮到不僅將來(lái)維護(hù)你代碼的開(kāi)發(fā)人員要看,而且你自己也可能要看。用Phil Haack大師的話來(lái)說(shuō)就是:“一旦一行代碼顯示屏幕上,你也就成了這段代碼的維護(hù)者”。因此,對(duì)于我們寫(xiě)得好(差)的注釋而言,我們將是第一個(gè)受益者(受害者)。
11. 同時(shí)更新代碼和注釋
如果注釋沒(méi)有跟隨代碼的變化而變化,及時(shí)是正確的注釋也沒(méi)有用。代碼和注釋?xiě)?yīng)該同步變化,否則這樣的注釋將對(duì)維護(hù)你代碼的開(kāi)發(fā)者帶來(lái)更大的困難。使用重構(gòu)工具時(shí)應(yīng)特別注意,它只會(huì)自動(dòng)更新代碼而不會(huì)修改注釋?zhuān)虼藨?yīng)該立即停止使用重構(gòu)工具。
12. 注釋的黃金規(guī)則:易讀的代碼
對(duì)于開(kāi)發(fā)者的一個(gè)基本原則就是:讓你的代碼為己解釋。雖然有些人懷疑這會(huì)讓那些不愿意寫(xiě)注釋的開(kāi)發(fā)者鉆空子,不過(guò)這樣的代碼真的會(huì)使你容易理解,還不需要額外維護(hù)注釋。例如在Fluid Interfaces文章里向你展示的代碼一樣:
Calculator calc = new Calculator();
calc.Set(0);
calc.Add(10);
calc.Multiply(2);
calc.Subtract(4);
Console.WriteLine( "Result: {0}", calc.Get() );
在這個(gè)例子中,注釋是不需要的,否則可能就違反了技巧4。為了使代碼更容易理解,你可以考慮使用適當(dāng)?shù)拿?(Ottinger's Rules里講解得相當(dāng)好),確保正確的縮進(jìn),并且采用coding style guides,違背這個(gè)技巧可能的結(jié)果就像是注釋在為不好的代碼apologize。
13. 與同事分享技巧
雖然技巧10已經(jīng)向我們表明了我們是如何從好的注釋中直接受益,這些技巧將讓所有開(kāi)發(fā)者受益,特別是團(tuán)隊(duì)中一起工作的同事。因此,為了編寫(xiě)出更容易理解和維護(hù)的代碼,嘗試自由的和你的同事分享這些注釋技巧。
好東西拿出來(lái)一起分享
今天突然遇到Active Sync 4.5 setup.msi這個(gè)工具在windows vista系統(tǒng)上裝不上這個(gè)問(wèn)題,查了一下,原來(lái)在windows vista上,不再采用Active Sync ,而是采用了drvupdate-x86.exe,具體名字叫做Microsoft Windows Mobile 設(shè)備中心6.1。微軟對(duì)其的描述是:
使用 Windows Mobile 設(shè)備中心,您可以建立新的合作關(guān)系,與 Windows Mobile 設(shè)備(Windows Mobile 2003 或更高版本)同步音樂(lè)、圖片和視頻,并對(duì)其進(jìn)行管理。Windows Mobile 設(shè)備中心與高效的商務(wù)數(shù)據(jù)同步平臺(tái)緊密結(jié)合,提供了令人耳目一新的用戶(hù)體驗(yàn)。Windows Mobile 設(shè)備中心可以幫助您快速建立新的合作關(guān)系,同步重要的商務(wù)信息(例如電子郵件、聯(lián)系人和日歷約會(huì)),輕松管理同步設(shè)置,以及在設(shè)備與 PC 間傳送商業(yè)文檔。
這個(gè)主要是用于vista系統(tǒng)的。下載地址是
http://www.microsoft.com/downloads/details.aspx?familyid=46F72DF1-E46A-4A5F-A791-09F07AAA1914&displaylang=zh-cn
也可以建議大家去搜一下,我是在新浪上下載的。
鏈接:http://down1.tech.sina.com.cn/download/down_contents/1183219200/35965.shtml
在csdn上閑逛,看到一個(gè)這樣的問(wèn)題:如何在PPC上實(shí)現(xiàn)多語(yǔ)言的程序列表中的名稱(chēng)在編譯時(shí)自動(dòng)切換
這個(gè)曾經(jīng)讓我相當(dāng)苦惱,看到這個(gè)自然不會(huì)放過(guò)。
一位牛人很簡(jiǎn)單的回答:
參考MUIHello 例子:
C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Smartphone SDK\Samples\CPP\Win32\Muihello\app\app.sln
C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Smartphone SDK\Samples\CPP\Win32\Muihello\German_resfile\german_resfile.sln
C:\Program Files\Windows CE Tools\wce500\Windows Mobile 5.0 Smartphone SDK\Samples\CPP\Win32\Muihello\Resfile\resfile.sln
Samples竟然有多語(yǔ)言的例子,讓我相當(dāng)?shù)捏@訝!
學(xué)習(xí)了一下,比我自己的方法簡(jiǎn)單很多,看來(lái)需要好好學(xué)習(xí)SDK中提供的例子。
這個(gè)例子會(huì)生成muihello.exe.0409.mui,這其實(shí)是一個(gè)DLL資源文件,有人說(shuō)這是一種命名規(guī)則。在不同的平臺(tái)上就調(diào)用不同的資源,實(shí)現(xiàn)多語(yǔ)言。
在resfile.cpp文件中
#include "windows.h"


/**//////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HANDLE hInstance, DWORD dwReason, LPVOID lpReserved)


{
return TRUE;
}
還有一個(gè)文件resfile.def
LIBRARY "muihello.exe.0409.mui"

EXPORTS

而且resource.h是共用一個(gè)的。
嘗試了一下,蠻好。
Burning by Maria Arredondo
Passion is sweet
Love makes weak
You said you cherished freedom so
You refused to let it go
Follow your fate
Love and hate
Never failed to seize the day
Don’t give yourself away
Oh when the night falls
And you’re all alone
In your deepest sleep
What are you dreaming of
My skin is still burning from your touch
Oh I just can’t get enough
I said I wouldn’t ask for much
But your eyes are dangerous
So the world keeps spinning in my head
Can we drop this masquerade
I can’t predict where it ends
If you’ re the rock I’ll crush against
Trapped in a crowd
Music is loud
I said I loved my freedom too
Now I’m not so sure I do
All eyes on you
Wings so true
Better quit while you're a head
Now I’m not so sure I am
Oh when the night falls
And you’re all alone
In your deepest sleep
What are you dreaming of
My skin is still burning from your touch
Oh I just can’t get enough
I said I wouldn’t ask for much
But your eyes are dangerous
So the world keeps spinning in my head
Can we drop this masquerade
I can’t predict where it ends
If you’ re the rock I’ll crush against
My soul, my heart
If you’re near or if you’re far
My life, my love
You can have it all
看到他們的在爭(zhēng)論很有意思,我不是很懂。
有利還是有弊呢?
ASSERT在DEBUG程序時(shí)候幫了太多太多忙,不過(guò)在ASSERT判斷傳入?yún)?shù)后,還需要if再按相同條件判斷一遍,不符合規(guī)則return,這樣才是正確的邏輯。但這樣代碼難看,且工作重復(fù)無(wú)趣,又容易出現(xiàn)差漏。
剛弄了個(gè)簡(jiǎn)單EXT_ASSERT宏,按我的理解應(yīng)該可以解決問(wèn)題,但不確定是否有漏洞,發(fā)出來(lái)大家一起瞄瞄。
#define RET_VOID
#define EX_ASSERT(exp, ret) {ASSERT(exp);if(!(exp))return(ret);}
BOOL CXXX::FunXXX(const data* p_data)
{
EXT_ASSERT(p_data, FALSE);//---- 返回BOOL型
}
int CXXX::FunXXX(const data* p_data)
{
EXT_ASSERT(p_data, -1);//---- 返回int型
}
const retdata* CXXX::FunXXX(const data* p_data)
{
EXT_ASSERT(p_data, NULL);//---- 返回NULL指針
}
retdata CXXX::FunXXX(const data* p_data)
{
EXT_ASSERT(p_data, retdata());//---- 返回空對(duì)象
}
void CXXX::FunXXX(const data* p_data)
{
EXT_ASSERT(p_data, RET_VOID);//---- 僅僅return
}