摘要: lua的VM執(zhí)行代碼是從lvm.c中的void luaV_execute(lua_State *L)開始:Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/-->void luaV_execute (lua_State *L)&n...
閱讀全文
posted @
2012-09-19 12:34 airtrack 閱讀(7197) |
評(píng)論 (5) |
編輯 收藏
C的NULL
在C語言中,我們使用NULL表示空指針,也就是我們可以寫如下代碼:
int *i = NULL;
foo_t *f = NULL;
實(shí)際上在C語言中,NULL通常被定義為如下:
#define NULL ((void *)0)
也就是說NULL實(shí)際上是一個(gè)void *的指針,然后吧void *指針賦值給int *和foo_t *的指針的時(shí)候,隱式轉(zhuǎn)換成相應(yīng)的類型。而如果換做一個(gè)C++編譯器來編譯的話是要出錯(cuò)的,因?yàn)镃++是強(qiáng)類型的,void *是不能隱式轉(zhuǎn)換成其他指針類型的,所以通常情況下,編譯器提供的頭文件會(huì)這樣定義NULL:
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
C++的0
因?yàn)镃++中不能將void *類型的指針隱式轉(zhuǎn)換成其他指針類型,而又為了解決空指針的問題,所以C++中引入0來表示空指針,這樣就有了類似上面的代碼來定義NULL。實(shí)際上C++的書都會(huì)推薦說C++中更習(xí)慣使用0來表示空指針而不是NULL,盡管NULL在C++編譯器下就是0。為什么C++的書都推薦使用0而不是NULL來表示空指針呢?我們看一個(gè)例子:
在foo.h文件中聲明了一個(gè)函數(shù):
void bar(sometype1 a, sometype2 *b);
這個(gè)函數(shù)在a.cpp、b.cpp中調(diào)用了,分別是:
a.cpp:

bar(a, b);

b.cpp:

bar(a, 0);

好的,這些代碼都是正常完美的編譯運(yùn)行。但是突然在某個(gè)時(shí)候我們功能擴(kuò)展,需要對(duì)bar函數(shù)進(jìn)行擴(kuò)展,我們使用了重載,現(xiàn)在foo.h的聲明如下:
void bar(sometype1 a, sometype2 *b);
void bar(sometype1 a, int i);
這個(gè)時(shí)候危險(xiǎn)了,a.cpp和b.cpp中的調(diào)用代碼這個(gè)時(shí)候就不能按照期望的運(yùn)行了。但是我們很快就會(huì)發(fā)現(xiàn)b.cpp中的0是整數(shù),也就是在overload resolution的時(shí)候,我們知道它調(diào)用的是void bar(sometype1 a, int i)這個(gè)重載函數(shù),于是我們可以做出如下修改讓代碼按照期望運(yùn)行:
bar(a, static_cast<sometype2 *>(0));
我知道,如果我們一開始就有bar的這兩個(gè)重載函數(shù)的話,我們會(huì)在一開始就想辦法避免這個(gè)問題(不使用重載)或者我們寫出正確的調(diào)用代碼,然而后面的這個(gè)重載函數(shù)或許是我們幾個(gè)月或者很長(zhǎng)一段時(shí)間后加上的話,那我們出錯(cuò)的可能性就會(huì)加大了不少。貌似我們現(xiàn)在說道的這些跟C++通常使用0來表示空指針沒什么關(guān)系,好吧,假設(shè)我們的調(diào)用代碼是這樣的:
foo.h
void bar(sometype1 a, sometype2 *b);
a.cpp

bar(a, b);

b.cpp

bar(a, NULL);

當(dāng)bar的重載函數(shù)在后面加上來了之后,我們會(huì)發(fā)現(xiàn)出錯(cuò)了,但是出錯(cuò)的時(shí)候,我們找到b.cpp中的調(diào)用代碼也很快可能忽略過去了,因?yàn)槲覀冇玫氖荖ULL空指針啊,應(yīng)該是調(diào)用的void bar(sometype1 a, sometype2 *b)這個(gè)重載函數(shù)啊。實(shí)際上NULL在C++中就是0,寫NULL這個(gè)反而會(huì)讓你沒那么警覺,因?yàn)镹ULL不夠“明顯”,而這里如果是使用0來表示空指針,那就會(huì)夠“明顯”,因?yàn)?是空指針,它更是一個(gè)整形常量。
在C++中,使用0來做為空指針會(huì)比使用NULL來做空指針會(huì)讓你更加警覺。
C++ 11的nullptr
雖然上面我們說明了0比NULL可以讓我們更加警覺,但是我們并沒有避免這個(gè)問題。這個(gè)時(shí)候C++ 11的nullptr就很好的解決了這個(gè)問題,我們?cè)贑++ 11中使用nullptr來表示空指針,這樣最早的代碼是這樣的,
foo.h
void bar(sometype1 a, sometype2 *b);
a.cpp

bar(a, b);

b.cpp

bar(a, nullptr);

在我們后來把bar的重載加上了之后,代碼是這樣:
foo.h
void bar(sometype1 a, sometype2 *b);
void bar(sometype1 a, int i);
a.cpp

bar(a, b);

b.cpp

bar(a, nullptr);

這時(shí)候,我們的代碼還是能夠如預(yù)期的一樣正確運(yùn)行。
在沒有C++ 11的nullptr的時(shí)候,我們?cè)趺唇鉀Q避免這個(gè)問題呢?我們可以自己實(shí)現(xiàn)一個(gè)(《Imperfect C++》上面有一個(gè)實(shí)現(xiàn)):
const
class nullptr_t
{
public:
template<class T>
inline operator T*() const
{ return 0; }
template<class C, class T>
inline operator T C::*() const
{ return 0; }
private:
void operator&() const;
} nullptr = {};
雖然這個(gè)東西被大家討論過很多次了,但是我覺得還是有必要再討論一下,畢竟在C++ 11還沒有普及之前,我們還是應(yīng)該知道怎么去避免問題,怎么很快的找到問題。
posted @
2012-09-16 01:08 airtrack 閱讀(18108) |
評(píng)論 (3) |
編輯 收藏
接上一篇:lua源碼剖析(一)
詞法分析
lua對(duì)與每一個(gè)文件(chunk)建立一個(gè)LexState來做詞法分析的context數(shù)據(jù),此結(jié)構(gòu)定義在llex.h中。詞法分析根據(jù)語法分析的需求有當(dāng)前token,有l(wèi)ookahead token,LexState結(jié)構(gòu)如圖:

其中token結(jié)構(gòu)中用int存儲(chǔ)實(shí)際token值,此token值對(duì)于單字符token(+ - * /之類)就表示自身,對(duì)于多字符(關(guān)鍵字等)token是起始值為257的枚舉值,在llex.h文件中定義:
#define FIRST_RESERVED 257
/*
* WARNING: if you change the order of this enumeration,
* grep "ORDER RESERVED"
*/enum RESERVED {
/* terminal symbols denoted by reserved words */ TK_AND = FIRST_RESERVED, TK_BREAK,
TK_DO, TK_ELSE, TK_ELSEIF, TK_END, TK_FALSE, TK_FOR, TK_FUNCTION,
TK_GOTO, TK_IF, TK_IN, TK_LOCAL, TK_NIL, TK_NOT, TK_OR, TK_REPEAT,
TK_RETURN, TK_THEN, TK_TRUE, TK_UNTIL, TK_WHILE,
/* other terminal symbols */ TK_CONCAT, TK_DOTS, TK_EQ, TK_GE, TK_LE, TK_NE, TK_DBCOLON, TK_EOS,
TK_NUMBER, TK_NAME, TK_STRING
};
token結(jié)構(gòu)中還有一個(gè)成員seminfo,這個(gè)表示語義信息,根據(jù)token的類型,可以表示數(shù)值或者字符串。
lex提供函數(shù)luaX_next和luaX_lookahead分別lex下一個(gè)token和lookahead token,在內(nèi)部是通過llex函數(shù)來完成詞法分析。
語法分析
lua語法分析是從lparser.c中的luaY_parser開始:
Closure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff,
Dyndata *dyd, const char *name, int firstchar) {
LexState lexstate;
FuncState funcstate;
Closure *cl = luaF_newLclosure(L, 1); /* create main closure */
/* anchor closure (to avoid being collected) */
setclLvalue(L, L->top, cl);
incr_top(L);
funcstate.f = cl->l.p = luaF_newproto(L);
funcstate.f->source = luaS_new(L, name); /* create and anchor TString */
lexstate.buff = buff;
lexstate.dyd = dyd;
dyd->actvar.n = dyd->gt.n = dyd->label.n = 0;
luaX_setinput(L, &lexstate, z, funcstate.f->source, firstchar);
mainfunc(&lexstate, &funcstate);
lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs);
/* all scopes should be correctly finished */
lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0);
return cl; /* it's on the stack too */
}
此函數(shù)創(chuàng)建一個(gè)closure并把LexState和FuncState初始化后調(diào)用mainfunc開始parse,其中FuncState表示parse時(shí)函數(shù)狀態(tài)信息的,如圖:

每當(dāng)parse到一個(gè)function的時(shí)候都會(huì)建立一個(gè)FuncState結(jié)構(gòu),并將它與所嵌套的函數(shù)通過prev指針串聯(lián)起來,body函數(shù)就是完成嵌套函數(shù)parse。
static void body (LexState *ls, exposed *e, int ismethod, int line) {
/* body -> `(' parlist `)' block END */
FuncState new_fs;
BlockCnt bl;
new_fs.f = addprototype(ls);
new_fs.f->linedefined = line;
open_func(ls, &new_fs, &bl);
checknext(ls, '(');
if (ismethod) {
new_localvarliteral(ls, "self"); /* create 'self' parameter */
adjustlocalvars(ls, 1);
}
parlist(ls);
checknext(ls, ')');
statlist(ls);
new_fs.f->lastlinedefined = ls->linenumber;
check_match(ls, TK_END, TK_FUNCTION, line);
codeclosure(ls, e);
close_func(ls);
}
FuncState中的f指向這個(gè)函數(shù)的Proto,Proto中保存著函數(shù)的指令、變量信息、upvalue信息等其它信息,Proto的結(jié)構(gòu)如圖:

k指向一個(gè)這個(gè)Proto中使用到的常量,code指向這個(gè)Proto的指令數(shù)組,Proto **p指向這個(gè)Proto內(nèi)部的Proto列表,locvars存儲(chǔ)local變量信息,upvalues存儲(chǔ)upvalue的信息,cache指向最后創(chuàng)建的closure,source指向這個(gè)Proto所屬的文件名,后面的size*分別表示前面各個(gè)指針指向的數(shù)組的大小,numparams表示固定的參數(shù)的個(gè)數(shù),is_vararg表示這個(gè)Proto是否是一個(gè)變參函數(shù),maxstacksize表示最大stack大小。
FuncState中的ls指向LexState,在LexState中有一個(gè)Dyndata的結(jié)構(gòu),這個(gè)結(jié)構(gòu)用于保存在parse一個(gè)chunk的時(shí)候所存儲(chǔ)的gt label list和label list以及所有active變量列表,其中g(shù)t label list存儲(chǔ)的是未匹配的goto語句和break語句的label信息,而label list存儲(chǔ)的是已聲明的label。待出現(xiàn)一個(gè)gt label的時(shí)候就在label list中查找是否有匹配的label,若出現(xiàn)一個(gè)label也將在gt label list中查找是否有匹配的gt。
LuaY_parser調(diào)用mainfunc開始parse一個(gè)chunk:
static void mainfunc (LexState *ls, FuncState *fs) {
BlockCnt bl;
expdesc v;
open_func(ls, fs, &bl);
fs->f->is_vararg = 1;
/* main function is always vararg */ init_exp(&v, VLOCAL, 0);
/* create and
*/ newupvalue(fs, ls->envn, &v);
/*
set environment upvalue */ luaX_next(ls);
/* read first token */ statlist(ls);
/* parse main body */ check(ls, TK_EOS);
close_func(ls);
}
在mainfunc中通過open_func函數(shù)完成對(duì)進(jìn)入某個(gè)函數(shù)進(jìn)行parse之前的初始化操作,每parse進(jìn)一個(gè)block的時(shí)候,將建立一個(gè)BlockCnt的結(jié)構(gòu)并與上一個(gè)BlockCnt連接起來,當(dāng)parse完一個(gè)block的時(shí)候就回彈出最后一個(gè)BlockCnt結(jié)構(gòu)。BlockCnt結(jié)構(gòu)中的其它變量的意思是:nactvar表示這個(gè)block之前的active var的個(gè)數(shù),upval表示這個(gè)block是否有upvalue被其它block訪問,isloop表示這個(gè)block是否是循環(huán)block。mainfunc中調(diào)用statlist,statlist調(diào)用statement開始parse語句和表達(dá)式。
statement分析語句采用的是LL(2)的遞歸下降語法分析法。在statement里面通過case語句處理各個(gè)帶關(guān)鍵字的語句,在default語句中處理賦值和函數(shù)調(diào)用的分析。語句中的表達(dá)式通過expr函數(shù)處理,其處理的BNF如下:
exp ::= nil | false | true | Number | String | ‘...’ | functiondef |
prefixexp | tableconstructor | exp binop exp | unop exp
expr函數(shù)調(diào)用subexpr函數(shù)完成處理。
static BinOpr subexpr (LexState *ls, expdesc *v, int limit) {
BinOpr op;
UnOpr uop;
enterlevel(ls);
uop = getunopr(ls->t.token);
if (uop != OPR_NOUNOPR) {
int line = ls->linenumber;
luaX_next(ls);
subexpr(ls, v, UNARY_PRIORITY);
luaK_prefix(ls->fs, uop, v, line);
}
else simpleexp(ls, v);
/* expand while operators have priorities higher than `limit' */
op = getbinopr(ls->t.token);
while (op != OPR_NOBINOPR && priority[op].left > limit) {
expdesc v2;
BinOpr nextop;
int line = ls->linenumber;
luaX_next(ls);
luaK_infix(ls->fs, op, v);
/* read sub-expression with higher priority */
nextop = subexpr(ls, &v2, priority[op].right);
luaK_posfix(ls->fs, op, v, &v2, line);
op = nextop;
}
leavelevel(ls);
return op; /* return first untreated operator */
}
當(dāng)分析exp binop exp | unop exp的時(shí)候lua采用的是算符優(yōu)先分析,其各個(gè)運(yùn)算符的優(yōu)先級(jí)定義如下:
static const struct {
lu_byte left; /* left priority for each binary operator */
lu_byte right; /* right priority */
} priority[] = { /* ORDER OPR */
{6, 6}, {6, 6}, {7, 7}, {7, 7}, {7, 7}, /* `+' `-' `*' `/' `%' */
{10, 9}, {5, 4}, /* ^, .. (right associative) */
{3, 3}, {3, 3}, {3, 3}, /* ==, <, <= */
{3, 3}, {3, 3}, {3, 3}, /* ~=, >, >= */
{2, 2}, {1, 1} /* and, or */
};
#define UNARY_PRIORITY 8 /* priority for unary operators */
代碼生成
lua代碼生成是伴隨著語法分析進(jìn)行的,指令類型Instruction定義在llimits.h中:
/*
** type for virtual-machine instructions
** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h)
*/
typedef lu_int32 Instruction;

Instruction是一個(gè)32位的整形數(shù)據(jù),其中0~5 bits表示optype,6~13 bits參數(shù)A,14~22 bits表示參數(shù)B,23~31 bits表示參數(shù)C,14~31 bits表示參數(shù)Bx或sBx,6~31 bits表示參數(shù)Ax。
代碼生成的函數(shù)聲明在lcode.h中,以luaK開頭,這一系列的函數(shù)大多都有expdesc *v的參數(shù),expdesc的結(jié)構(gòu)定義在lparser.h,如下:
typedef struct expdesc {
expkind k;
union {
struct { /* for indexed variables (VINDEXED) */
short idx; /* index (R/K) */
lu_byte t; /* table (register or upvalue) */
lu_byte vt; /* whether 't' is register (VLOCAL) or upvalue (VUPVAL) */
} ind;
int info; /* for generic use */
lua_Number nval; /* for VKNUM */
} u;
int t; /* patch list of `exit when true' */
int f; /* patch list of `exit when false' */
} expdesc;
expdesc中的t和f分別表示表達(dá)式為true和false時(shí),待回填跳轉(zhuǎn)指令的下標(biāo)。k表示表達(dá)式的類型,u表示對(duì)應(yīng)類型的數(shù)據(jù)。
代碼生成過程中根據(jù)表達(dá)式類型做相應(yīng)的代碼生成操作,lua中每個(gè)函數(shù)最大有250個(gè)寄存器,表達(dá)式的計(jì)算就是選擇這些寄存器存放并生成數(shù)據(jù),而寄存器的下標(biāo)是在代碼生成階段選擇好的,寄存器的釋放是根據(jù)變量和表達(dá)式的生命周期結(jié)束的時(shí)候釋放。代碼生成過程會(huì)將變量的生命周期的起始pc和結(jié)束指令pc分別存放在Proto中的LocVar的startpc和endpc里面,供調(diào)試使用。
posted @
2012-08-12 17:28 airtrack 閱讀(8600) |
評(píng)論 (0) |
編輯 收藏
很早就想讀lua的源碼,也曾很多次瀏覽過大概。不過我一直沒有深入去讀,一是想自己在讀lua源碼之前,僅憑自己對(duì)lua使用的理解自己先實(shí)現(xiàn)一個(gè)簡(jiǎn)單的lua子集,二是我覺得自己實(shí)現(xiàn)過lua的子集之后也能幫助自己更容易的理解lua源碼。前段時(shí)間,花了幾個(gè)月的業(yè)余時(shí)間,實(shí)現(xiàn)了一個(gè)簡(jiǎn)單粗糙的lua子集(https://github.com/airtrack/luna)之后,我覺得現(xiàn)在可以開始讀lua的源碼了。
從lua.c的main函數(shù)開始,lua.c是一個(gè)stand-alone的解釋器,編譯完就是一個(gè)交互式命令行解釋器,輸入一段lua代碼,然后執(zhí)行并返回結(jié)果,也可以執(zhí)行一個(gè)lua文件。
main:
/* call 'pmain' in protected mode */
lua_pushcfunction(L, &pmain);
lua_pushinteger(L, argc); /* 1st argument */
lua_pushlightuserdata(L, argv); /* 2nd argument */
status = lua_pcall(L, 2, 1, 0);
result = lua_toboolean(L, -1); /* get result */
main函數(shù)創(chuàng)建了lua_State之后就按照調(diào)用C導(dǎo)出給lua函數(shù)的方式調(diào)用了pmain函數(shù)。pmain函數(shù)中通過lua棧獲取到命令行的argc和argv參數(shù)之后,對(duì)參數(shù)進(jìn)行分析后,主要可以分為兩個(gè)分支,一個(gè)處理交互命令行,一個(gè)處理文件。dotty出來交互命令行,handle_script處理lua文件。
handle_script:
status = luaL_loadfile(L, fname);
lua_insert(L, -(narg+1));
if (status == LUA_OK)
status = docall(L, narg, LUA_MULTRET);
else
lua_pop(L, narg);
在handle_script中先loadfile,然后docall。
loadfile會(huì)產(chǎn)生一個(gè)什么東西在棧上呢?寫過lua的程序的人估計(jì)都會(huì)了解到下面這段lua代碼:
local f = load(filename)
f()
load會(huì)將文件chunk編譯成一個(gè)function,然后我們就可以對(duì)它調(diào)用。如果我們?cè)敿?xì)看lua文檔的話,這個(gè)函數(shù)可以帶有upvalues,也就是這個(gè)函數(shù)其實(shí)是一個(gè)閉包(closure)。按照我自己實(shí)現(xiàn)的那個(gè)粗糙的lua子集的方式的話,每個(gè)運(yùn)行時(shí)期的可調(diào)用的lua函數(shù)都是閉包。
#define luaL_loadfile(L,f) luaL_loadfilex(L,f,NULL)
luaL_loadfilex:
if (filename == NULL) {
lua_pushliteral(L, "=stdin");
lf.f = stdin;
}
else {
lua_pushfstring(L, "@%s", filename);
lf.f = fopen(filename, "r");
if (lf.f == NULL) return errfile(L, "open", fnameindex);
}
if (skipcomment(&lf, &c)) /* read initial portion */
lf.buff[lf.n++] = '\n'; /* add line to correct line numbers */
if (c == LUA_SIGNATURE[0] && filename) { /* binary file? */
lf.f = freopen(filename, "rb", lf.f); /* reopen in binary mode */
if (lf.f == NULL) return errfile(L, "reopen", fnameindex);
skipcomment(&lf, &c); /* re-read initial portion */
}
if (c != EOF)
lf.buff[lf.n++] = c; /* 'c' is the first character of the stream */
status = lua_load(L, getF, &lf, lua_tostring(L, -1), mode);
luaL_loadfile是一個(gè)宏,實(shí)際是luaL_loadfilex函數(shù),在luaL_loadfilex函數(shù)中,我們發(fā)現(xiàn)是通過調(diào)用lua_load函數(shù)實(shí)現(xiàn),lua_load的函數(shù)原型是:
LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data, const char *chunkname, const char *mode);
定義在lapi.c中,它接受一個(gè)lua_Reader的函數(shù)并把data作為這個(gè)reader的參數(shù)。在luaL_loadfilex函數(shù)中傳給lua_load作為reader是一個(gè)static函數(shù)getF,getF通過fread讀取文件。
lua_load:
ZIO z;
int status;
lua_lock(L);
if (!chunkname) chunkname = "?";
luaZ_init(L, &z, reader, data);
status = luaD_protectedparser(L, &z, chunkname, mode);
在函數(shù)lua_load中,又將lua_Reader和data通過luaZ_init函數(shù)把數(shù)據(jù)綁定到ZIO的結(jié)構(gòu)中,ZIO是buffered streams。之后調(diào)用luaD_protectedparser,此函數(shù)定義在ldo.c中,在這個(gè)函數(shù)中,我們發(fā)現(xiàn)它使用了構(gòu)造lua_Reader和data的方式構(gòu)造了調(diào)用函數(shù)f_parser和它的數(shù)據(jù)SParser,并將它們傳給luaD_pcall,luaD_pcall的功能是在protected模式下用SParser數(shù)據(jù)調(diào)用f_parser函數(shù),因此我們只需追蹤f_parser函數(shù)即可。
luaD_protectedparser:
status = luaD_pcall(L, f_parser, &p, savestack(L, L->top), L->errfunc);
f_parser:
if (c == LUA_SIGNATURE[0]) {
checkmode(L, p->mode, "binary");
cl = luaU_undump(L, p->z, &p->buff, p->name);
}
else {
checkmode(L, p->mode, "text");
cl = luaY_parser(L, p->z, &p->buff, &p->dyd, p->name, c);
}
f_parser通過數(shù)據(jù)頭的signature來判斷讀取的數(shù)據(jù)是binary還是text的,如果是binary的數(shù)據(jù),則調(diào)用luaU_undump來讀取預(yù)編譯好的lua chunks,如果是text數(shù)據(jù),則調(diào)用luaY_parser來parse lua代碼。我們發(fā)現(xiàn)luaU_undump和luaY_parser函數(shù)的返回值都是Closure *類型,這個(gè)剛好就和我們前面預(yù)計(jì)的一樣,一個(gè)chunk load之后返回一個(gè)閉包。
進(jìn)入luaY_parser函數(shù)后,就調(diào)用了一個(gè)static的mainfunc開始parse lua代碼。
仔細(xì)回顧上面看過的函數(shù),我們會(huì)發(fā)現(xiàn)每個(gè)C文件的導(dǎo)出函數(shù)都會(huì)使用lua開頭,如果沒有l(wèi)ua開頭的函數(shù)都是static函數(shù)。并且我們會(huì)發(fā)現(xiàn)lua后的大寫前綴可以標(biāo)識(shí)這個(gè)函數(shù)所屬的文件:
luaL_loadfile luaL_loadfilex L應(yīng)該是library的意思,屬于lauxlib
luaD_protectedparser luaD_pcall D是do的意思,屬于ldo
luaU_undump U 是undump的意思,屬于lundump
luaY_parser Y 是代表yacc的意思,lua的parser最早是用過yacc生成的,后來改成手寫,名字也保留下來,屬于lparser
其它的lua函數(shù)也都有這個(gè)規(guī)律。
posted @
2012-07-19 18:24 airtrack 閱讀(12542) |
評(píng)論 (3) |
編輯 收藏
vimer、emacser的優(yōu)越感
曾幾何時(shí),剛學(xué)編程沒多久,網(wǎng)上看到一群“牛人”吹噓說世界上有三種編輯器:一種是vim,一種是emacs,一種是其它。
當(dāng)時(shí)看到各種介紹vim和emacs的文章都是頂禮膜拜的,希望自己哪天也能成為那種能玩的動(dòng)“神器”。一直是水平不夠或者其它原因,沒學(xué)會(huì)。3年多前看到一個(gè)vim的視頻,當(dāng)時(shí)下狠心終于把vim學(xué)會(huì)了,當(dāng)然有之前一兩年斷斷續(xù)續(xù)學(xué)vim的基礎(chǔ)的幫助。自從學(xué)會(huì)了vim之后我也加入到了vimer行列,終于學(xué)會(huì)了“神器”編輯器。終于可以在別人討論其它編輯器的時(shí)候回復(fù)一句裝逼的“vimer飄過”的語句。前輩們是說vim、emacs高效,因?yàn)槟銓W(xué)會(huì)了之后,你的手不需要離開主鍵盤區(qū)域。其實(shí)在我看來這完全不是理由,其它編輯器的各種快捷鍵同樣能夠保證你不離開主鍵盤區(qū)域完成編輯功能,只不過普通編輯器不會(huì)強(qiáng)迫你學(xué)習(xí)快捷鍵,而vim和emacs是你必須學(xué)會(huì)快捷鍵才能夠使用。這時(shí)“牛人”也許會(huì)說vim和emacs都有超強(qiáng)的定制性,可以定制你想要的“任何”功能。看起來是很牛逼的,vim和emacs是有不少很強(qiáng)力的插件,可以把它們定制的很強(qiáng)力,但是要說到“任何”那也只是停留在理論上。C++自動(dòng)提示功能始終都是vim和emacs的痛,幸好有了clang,自動(dòng)提示能力終于上了一個(gè)檔次,但是那流暢程度和VA比起來還是要差一點(diǎn),畢竟是基于單個(gè)文件分析(每次改動(dòng)都會(huì)重新分析整個(gè)文件),不像VA那樣是整個(gè)工程分析。至于C++的重構(gòu)功能,到現(xiàn)在vim和emacs都沒有很好的實(shí)現(xiàn),不要說重構(gòu)在C++里面沒有用,至少我覺得rename和extract method還是很有用的。vim和emacs其實(shí)沒有牛人吹噓的那么神奇,當(dāng)然它們確實(shí)是很優(yōu)秀的編輯器。最近一段時(shí)間我在減少使用vim,是因?yàn)榻?jīng)常敲擊ctrl鍵導(dǎo)致左手小指有時(shí)會(huì)疼痛。這個(gè)問題也許會(huì)在emacser上面更加嚴(yán)重,emacser都是迫切需要“腳踏板”的。
vimer和emacser的優(yōu)越感是從前輩“牛人”那里聽來,費(fèi)勁力氣學(xué)會(huì),終于可以對(duì)沒學(xué)會(huì)的人來上一句“你的編輯器是其它”產(chǎn)生的。
蘋果系的優(yōu)越感
蘋果的產(chǎn)品通常比一般的產(chǎn)品有著更貴的價(jià)格,通常用戶體驗(yàn)比起一般的產(chǎn)品也確實(shí)要好,這往往成為某些裝逼人事的裝逼利器。當(dāng)一部分人用上了“先進(jìn)”的蘋果產(chǎn)品,開始寫各種文章炫耀蘋果的優(yōu)越性,比起其它怎么怎么好,使得很多沒有試用過蘋果產(chǎn)品的人心生向往,費(fèi)盡力氣也要體驗(yàn)體驗(yàn)。當(dāng)這些人費(fèi)盡力氣使用了蘋果的產(chǎn)品后,有很大一部分人自然的覺得自己用上了高端的產(chǎn)品,往往產(chǎn)生優(yōu)越感。有不少使用Macbook Pro的人說使用Macbook Pro再也沒有關(guān)過機(jī),什么東西都是合上就走,并以此產(chǎn)生對(duì)Windows的優(yōu)越感,說Windows是不可能做到。然而我身邊就有一個(gè)同事使用thinkpad,裝的是Windows XP,而他的機(jī)器一年都是沒關(guān)過機(jī),都是合上就走的,這更別說Windows 7了。我自己從去年開始使用Macbook air,剛開始使用第一周死過一次機(jī),后來也出現(xiàn)過一次死機(jī)。我覺得Macbook air是不錯(cuò),但是還不至于說甩開其它產(chǎn)品幾條街,讓人產(chǎn)生強(qiáng)烈的優(yōu)越感。
使用蘋果系的人產(chǎn)生優(yōu)越感往往是因?yàn)樽约焊冻隽吮容^大的一部分資金后,看到產(chǎn)品的不少優(yōu)點(diǎn)之后就開始無視對(duì)于其它產(chǎn)品的缺點(diǎn),從而產(chǎn)生一種高貴的優(yōu)越感。
Linux程序員的優(yōu)越感
有不少Linux程序員,覺得自己是Linux程序員能干不少牛逼的事,能看到優(yōu)秀的源碼。就連調(diào)用系統(tǒng)調(diào)用都能產(chǎn)生優(yōu)越感,說Linux的系統(tǒng)調(diào)用簡(jiǎn)單明了,比起Windows的API來說簡(jiǎn)單。這當(dāng)然是個(gè)優(yōu)點(diǎn),但這就能讓人產(chǎn)生優(yōu)越感。而往往即懂Windows又懂Linux的人的卻能夠更好更正確的認(rèn)識(shí)各個(gè)系統(tǒng)的優(yōu)缺點(diǎn)。我了解到一些Linux程序員會(huì)產(chǎn)生優(yōu)越感,有不少是曾今學(xué)習(xí)Windows編程,發(fā)現(xiàn)自己沒能學(xué)好(往往是學(xué)習(xí)GUI編程沒學(xué)好),然后看到很多網(wǎng)上牛人都使用Linux,然后轉(zhuǎn)移到Linux潛心學(xué)習(xí),編寫命令行程序,終于修煉成功,之后就開始噴Windows多么不好,進(jìn)而產(chǎn)生優(yōu)越感。
C程序員的優(yōu)越感
C程序員的優(yōu)越感的產(chǎn)生有點(diǎn)類似Linux程序員,而往往C程序員也就是Linux程序員。有了Linux的優(yōu)越感之后,更加的認(rèn)為只要有Linux和C就能解決所有問題,只要比C更復(fù)雜的東西都是不值得的。而這些C程序員自然而然的把優(yōu)越感產(chǎn)生建立在C++之上,而且是這個(gè)也是有一定的相似性,也是帶著C的思維學(xué)習(xí)C++,發(fā)現(xiàn)不少C++的東西不是按照他想象的那樣運(yùn)作之后,就開始鄙視C++最終又回歸為C,而往往也產(chǎn)生對(duì)C++程序員的優(yōu)越感。不過再我看來,如果能夠成為一個(gè)優(yōu)秀的C++程序員,你讓他回去寫C代碼,他同樣能夠?qū)懗鰞?yōu)秀的C代碼來。C程序員的優(yōu)越感其實(shí)有些可悲,往往是自己短視,可以不喜歡不使用一種語言,但是這完全不是產(chǎn)生優(yōu)越感的理由。
技術(shù)等級(jí)的優(yōu)越感
一般公司都有技術(shù)等級(jí)之分,高級(jí)工程師一般工作經(jīng)驗(yàn)比普通工程師要豐富一點(diǎn),抑或是在某些方面比較擅長(zhǎng)。而他們對(duì)待普通工程師的時(shí)候往往產(chǎn)生一種“我什么都應(yīng)該比普通工程師懂的優(yōu)越感”,跟普通工程師討論問題的時(shí)候往往帶著一種高級(jí)工程師的優(yōu)越感,覺得普通工程師的各個(gè)方面都不如自己的感覺,因而形成一種嚴(yán)格的等級(jí)制度,時(shí)間長(zhǎng)了之后就變成了一種“文化”。這種優(yōu)越感似乎是有傳遞性的,等那些普通工程師終于熬成高級(jí)之后也開始對(duì)后來的普通工程師產(chǎn)生優(yōu)越感。
還有其它不少情況很多人會(huì)對(duì)某些人某些東西產(chǎn)生優(yōu)越感,這種優(yōu)越感的產(chǎn)生一般都是因?yàn)楦冻隽烁嗟哪硺訓(xùn)|西之后,自然的對(duì)事物的分級(jí)而產(chǎn)生,覺得自己的層級(jí)更高一點(diǎn),自然而然的產(chǎn)生了優(yōu)越感。當(dāng)這種優(yōu)越感開始在一定范圍內(nèi)開始傳播之后,對(duì)于某些曾今不能體會(huì)到優(yōu)越感的人同樣付出了更多的某樣?xùn)|西之后,像病毒式的也感染了這種優(yōu)越感。使得這種優(yōu)越感一直往下傳遞。
最近發(fā)現(xiàn)身邊和網(wǎng)上不少這種優(yōu)越感案例,有感而發(fā),寥寥幾筆。
posted @
2012-05-15 19:17 airtrack 閱讀(4213) |
評(píng)論 (21) |
編輯 收藏
摘要: BitWave的Host:
源碼放在github上,采用NEW BSD LICENSE發(fā)布。地址:https://github.com/airtrack/bitwave
閱讀全文
posted @
2011-05-29 17:39 airtrack 閱讀(4681) |
評(píng)論 (8) |
編輯 收藏
用Lua也有大半年了,從用Lua開始就想寫個(gè)Lua調(diào)試器,不過由于種種原因沒寫,這周上班抽了點(diǎn)時(shí)間寫了(我承認(rèn)上班偷懶了,不過多是休息時(shí)間)。(點(diǎn)此下載)
Lua本身沒有提供調(diào)試器,不過它自帶了一個(gè)debug庫,提供了基本的變量值獲取和代碼執(zhí)行hook,有了這些基本功能要寫一個(gè)調(diào)試器不難。
此調(diào)試器根據(jù)調(diào)試方式分為normal、step in、step over、next line四種mode,分別對(duì)應(yīng)斷點(diǎn)、步進(jìn)、跳出函數(shù)、執(zhí)行下行的功能。斷點(diǎn)類型分為行斷點(diǎn)和函數(shù)斷點(diǎn),分別在執(zhí)行到相應(yīng)行和相應(yīng)函數(shù)的時(shí)候斷下。在斷下的時(shí)候就可以打印和修改變量,通過建立一個(gè)新的chunk并將環(huán)境設(shè)置成相應(yīng)函數(shù)的環(huán)境,再執(zhí)行chunk來獲取和修改變量。
因?yàn)槭敲钚械模诿钚羞€沒有機(jī)會(huì)添加斷點(diǎn)的時(shí)候,要添加斷點(diǎn)就要通過debugger.addfuncbreak和debugger.addlinebreak來添加函數(shù)和行斷點(diǎn),通常Lua是用于C++的腳本語言,因此程序通常是有一個(gè)可以直接執(zhí)行Lua腳本指令的入口,這樣的入口就可以打下第一個(gè)斷點(diǎn),這樣在斷下斷點(diǎn)后就可以在命令提示符下做所有的操作了。
代碼是上班抽時(shí)間寫的,寫的很隨意,也沒多少注釋,權(quán)當(dāng)玩具吧。目前的功能基本能滿足我的要求了,也不打算繼續(xù)改進(jìn)了。調(diào)試器是用來幫助找錯(cuò)誤,不要過分依賴調(diào)試器。Robert C. Martin說Debuggers are a wasteful Timesink。雖說有些偏激,但是不無道理。
posted @
2011-01-01 01:44 airtrack 閱讀(4792) |
評(píng)論 (3) |
編輯 收藏