一个小程序的调试过程——源程序注释过滤器
2006-07-26 21:05
399 查看
BS的《The C++ programming language》上有一道习题:写一个程序,使它能剥掉C++程序里的所有注释。题目要求从cin读进写入cout,但我还是觉得用文件来读写更有用一点。于是就开始构思这个小东西。
C++的注释分两种。/* */和//,两种注释各有特点。第一种虽然有些像“括号”,但不能嵌套,所以就不考虑堆栈的问题了。//注释符作用到本行结尾,如果在注释最后加一个/,就可以把下一行也注释掉。另外,字符串里的注释符号是不起任何作用的。
看起来情况很多、很复杂,但如果引入一个状态量,根据当前字符所处的状态来判断该如何处理,这个问题还是可以解决的。搞一个枚举出来:
enum Flag{ NORMAL, INSTRING, INOLDCOMMENT, INNEWCOMMENT };
我划分了四种情况,也就是:正常情况(初始就是这种情况,须进一步检测注释符以及引号),在字符串里的情况(只检测是否到了“后引号”处,而不用检测注释符号),在老注释符里的情况(只管检测*/,其余一概不管),在新注释符里的情况(直接挖掉一行,并检测这行末尾是不是/,如果是,就再挖掉一行)。
开始的想法是,一个一个字符读进,然后就写一个char NextValidChar( void )函数,用来返回下一个“合法”的字符。也就是说,把ifstream和ofstream都搞成全局的,这个函数如果遇到“合法的字符”(注释以外的)就直接返回这个字符,如果是注释内的字符就递归调用本身——也就是说一直调用它本身直到遇到“合法”字符为止。后来才发现,这样效率太低了——过一会再说这个问题。毕竟,要先make it work, make it right,才能make it efficient。
这里先埋一个伏笔:我使用了标准库istream对象的一个成员函数:putback( char ),用这个函数的作用在于当我检测/*和//时候,如果/后面跟的不是*或者/,我就要把这个字符放回输入流并返回前面的那个/,以免把不是注释的这个字符给漏掉了。按理说,在检测*/的时候也应该这样做,但我开始武断地认为就算漏掉了也没关系——它反正是要被“丢弃”的字符。这就是后来困扰我的问题的真正根源。
以下是我第一个版本的程序,以上的分析过程花了我半个小时,下面的coding也是半个小时:
//LiuKai @ HUST
//2006-7-26
/**********************************************************************************
KillComment4.cpp用来剥掉C++源程序中的所有注释——exec6.6.22
本程序读入一个cpp文件,并将剥去注释的程序写入另一个文件
***********************************************************************************/
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
ifstream is;
ofstream os;
string inFileName = "comment.cpp";
string outFileName = "comment.txt";
enum Flag{ NORMAL, INSTRING, INOLDCOMMENT, INNEWCOMMENT };
Flag status = NORMAL;
char NextValidChar( void )
{
char next = 0;
is.get(next);
if( next == 0 )
return 0;
switch( status )
{
case NORMAL:
{
if( next == '/' )
{
char after;
is.get(after);
if( after == '*' )
{
status = INOLDCOMMENT;
return NextValidChar();
}
else if( after == '/' )
{
status = INNEWCOMMENT;
return NextValidChar();
}
else
{
is.putback(after);
return next;
}
}//end condition /
else if( next == '/"' )
{
status = INSTRING;
return next;
}//end condition "
else
{
status = NORMAL;
return next;
}//other conditions
}
break;
case INSTRING:
{
if( next == '/"' )
{
status = NORMAL;
}
return next;
}
break;
case INOLDCOMMENT:
{
if( next == '*' )
{
char after;
is.get(after);
if( after == '/' )
{
status = NORMAL;
return NextValidChar();
}
}//end condition *
return NextValidChar();
}
break;
case INNEWCOMMENT:
{
string rubbish;
getline(is,rubbish);
if( *(rubbish.end()-1)=='//' )
{
getline(is,rubbish);
}
status = NORMAL;
return '/n';
}
break;
default:
cerr << "Impossible!" << endl;
return 0;
}
}
int main( void )
{
is.open( inFileName.c_str() );
os.open( outFileName.c_str() );
char ch;
ch = NextValidChar();
while( ch )
{
os << ch;
ch = NextValidChar();
}
is.close();
os.close();
return 0;
}
客观的说,这个程序写得是很丑的——有一个超长的函数,就是那个NextValidChar(),coding过程中没有碰到任何语法上的问题(那是当然),第一次测试找了一个不久之前写的小程序,把它读入以后又生成了一个txt文件。很幸运(或者说很不幸)我的程序这次工作得很好,把那个源程序里的注释剥得干干净净,任何情况都符合我的预期,而且代码的格式还保证得很漂亮。我当时的感觉超high,因为我几乎没有遇到任何错误就解决了这个问题——高高兴兴吃饭去了。
晚饭回来还又“欣赏”了一遍这个程序,觉得应该多测试几个文件——我是对的。把读入文件名改成另一个源文件,我立马傻了眼:生成的文件是空的!难道是没读进去?果然,文件名打错了。我松了一口气,改正文件名,再运行一次。怪了,怎么生成的文件里面还是什么都没有?仔细察看生成文件的属性,发现不是空的了,而是六个字节。这就说明文件是读入了,但是剥得有问题。
按照我的注释风格,文件一开头就是这样的:
//LiuKai @ HUST
//2006-7-26
/**********************************************************************************
KillComment.cpp用来剥掉C++源程序中的所有注释——exec6.6.22
本程序读入一个cpp文件,并将剥去注释的程序写入另一个文件
***********************************************************************************/
我用跟踪调试,发现程序在按照我的意思把前两个新式注释替换成回车以后,在读下面那个老式注释的时候返回了一个0,当然是递归返回的,我只能判断问题出在那个老式注释之后。为了证实这个想法,我在第三行添加了一些文字,再次运行后那些文字被原样写到了目标文件,后面的依然是空白。
我测试的两个文件的注释风格完全一样。为什么第一个可以工作得很好但第二个就会出问题呢??我比较了这两个测试文件的注释,突然想到:是不是汉字的问题?我的程序是一个字节一个字节的读进的,碰到0就结束。而汉字占两个字节,会不会有一个字节是0呢?至于第一个文件可以工作也很好解释,那就是没有碰到这样的汉字。
怎么解决这个问题呢?也就是说,怎样区分文件结束标志的那个0和汉字里可能有的那个0呢?呵呵,难不倒我。汉字里即时有字节0,也不可能连着两个字节都是零,当然极端一点可能前一个汉字的低字节和后一个汉字的高字节都是0,那么,总不至于有三个零连在一起的情况吧!而文件结束不一样,文件结束以后,无论对ifstream调用多少次get函数得到的结果一定都是0。这样的话,我就可以在函数判断到该返回0的时候加一个判断,向后在读两个字符,设置一个叫chinese的bool标志。如果都为零,则说明是文件结束,不是汉字的原因,chinese就为false,反之,chinese为true。然后在main函数的循环条件里加上这个东西,也就是说返回的字符是0而且chinese是false时才能退出循环(才是真正的文件结尾)。
改过以后我又测试了一遍,结果和第一个版本的结果完全一样——清者自清,浊者自浊。似乎看不到任何区别。是我的汉字判断有问题,还是问题出在别处?为了弄清到底是不是这个问题,我把测试失败的那个源文件拷贝了一个副本,并把副本里的注释符号手工去掉,只剩汉字。再次测试,发现我两个版本的程序对于副本里的汉字都能顺利地读出来。这时候,我就确定我的错误和汉字无关。
和汉字无关,那就一定和老式注释符/**/有关。回到第一个版本。我耐心察看函数的调用过程,有了一点发现:我的函数在遇到注释里的字符的时候,在跳过这个字符之外还要递归调用自身,当找到一个“合法”字符时再逐级传上来——这是很危险的:注释长度动辄几百个字符,我这样岂不是让函数递归几百层么?且不说效率极低,还有堆栈溢出的可能。会不会就是因为堆栈溢出而导致的程序结束,而只能把溢出之前的东西写进文件呢?很有可能。调用方式必须改。把函数从递归改为非递归,看起来是一件伤筋动骨的事情。可这个函数比较特殊,我所用的“递归”只是为了写程序方便,这个过程从逻辑上来说是不递归的。我只要先设置一个bool量skip,用来描述当前字符是否需要写入目标文件(即前面所说的“合法”),然后让NextValidChar()函数中所有递归调用(即return NextValidChar())的地方都把这个布尔量设为true(即需要skip),然后简单的返回当前值。在向目标文件写字符式需要检查这个量。这样,我就成功地把函数的调用方式改了过来。
这是第三个版本了。但令我沮丧的是,它竟然还和第一个版本得到完全一样的结果。当然我也不后悔我的改动,毕竟它能和前两个版本工作得一样好(对有些源文件结果是正确的),而且效率肯定很高,又算是歪打正着解决了一个潜在的问题吧。
那么真正的问题到底在哪里呢?当我翻动程序看到前面那个putback函数时,终于意识到问题的所在。我在扫描老式注释的结束符*/的时候,没有用skip。我最初是这样想的,*后面如果是/的话,那么它就是结束符,如果不是的话,也用不着把提前读到的*放回输入流,因为这些都是注释里面的字符,迟早是要被丢掉的。这种想法在通常的“*/”情况下是没有问题的,在我经常使用的“***********/”情况下当“*”为奇数个的时候也是没有问题的。问题就出在当星号为偶数个的时候。这时,函数会“正常地”读到倒数第二个星号处,然后向后探索看是否遇到“/”,结果不是/而是最后的那个星号,也就是读到了“**”。由于我没有把多读的那个星号放回输入流,下次读到的就是那个单独的/,而且在以后的字符里,再也没有“*/”出现(这种注释我一般只放在最前面),于是,整个文件都被当成了注释的内容,因而全部被skip了。
真正的解决问题只需要加一条语句,但解决这个问题我却走了这么多弯路。但这些弯路都没白走,它们的确促使我更深入地思考这个程序并且改进其中不合理之处。以下是我最后版本的程序:
//LiuKai @ HUST
//2006-7-26
/**********************************************************************************
KillComment.cpp用来剥掉C++源程序中的所有注释——exec6.6.22
本程序读入一个cpp文件,并将剥去注释的程序写入另一个文件
**********************************************************************
af81
*************/
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
ifstream is;
ofstream os;
string inFileName = "A_IConvert.cpp";
string outFileName = "comment.cpp";
enum Flag{ NORMAL, INSTRING, INOLDCOMMENT, INNEWCOMMENT };
Flag status = NORMAL;
bool skip = false;
bool chinese = false;
char NextValidChar( void )
{
char next = 0;
is.get(next);
if( next == 0 )
{
//处理中文字符
char testc1,testc2;
is.get(testc1);
is.get(testc2);
if( testc1!=0 || testc2!= 0 )
{
chinese = true;
is.putback(testc2);
is.putback(testc1);
}
return 0;
}
switch( status )
{
case NORMAL:
{
if( next == '/' )
{
char after;
is.get(after);
if( after == '*' )
{
status = INOLDCOMMENT;
skip = true;
return next;
}
else if( after == '/' )
{
status = INNEWCOMMENT;
skip = true;
return next;
}
else
{
is.putback(after);
skip = false;
return next;
}
}//end condition /
else if( next == '/"' )
{
status = INSTRING;
skip = false;
return next;
}//end condition "
else
{
status = NORMAL;
skip = false;
return next;
}//other conditions
}
break;
case INSTRING:
{
if( next == '/"' )
{
status = NORMAL;
}
skip = false;
return next;
}
break;
case INOLDCOMMENT:
{
if( next == '*' )
{
char after;
is.get(after);
if( after == '/' )
{
status = NORMAL;
skip = true;
return next;
}
else
{
is.putback(after);
}
}//end condition *
skip = true;
return next;
}
break;
case INNEWCOMMENT:
{
string rubbish;
getline(is,rubbish);
if( *(rubbish.end()-1)=='//' )
{
getline(is,rubbish);
}
status = NORMAL;
skip = false;
return '/n';
}
break;
default:
cerr << "Impossible!" << endl;
return 0;
}
}
int main( void )
{
is.open( inFileName.c_str() );
os.open( outFileName.c_str() );
char ch;
ch = NextValidChar();
while( ch && !chinese )
{
if(!skip)
os << ch;
ch = NextValidChar();
}
is.close();
os.close();
return 0;
}
C++的注释分两种。/* */和//,两种注释各有特点。第一种虽然有些像“括号”,但不能嵌套,所以就不考虑堆栈的问题了。//注释符作用到本行结尾,如果在注释最后加一个/,就可以把下一行也注释掉。另外,字符串里的注释符号是不起任何作用的。
看起来情况很多、很复杂,但如果引入一个状态量,根据当前字符所处的状态来判断该如何处理,这个问题还是可以解决的。搞一个枚举出来:
enum Flag{ NORMAL, INSTRING, INOLDCOMMENT, INNEWCOMMENT };
我划分了四种情况,也就是:正常情况(初始就是这种情况,须进一步检测注释符以及引号),在字符串里的情况(只检测是否到了“后引号”处,而不用检测注释符号),在老注释符里的情况(只管检测*/,其余一概不管),在新注释符里的情况(直接挖掉一行,并检测这行末尾是不是/,如果是,就再挖掉一行)。
开始的想法是,一个一个字符读进,然后就写一个char NextValidChar( void )函数,用来返回下一个“合法”的字符。也就是说,把ifstream和ofstream都搞成全局的,这个函数如果遇到“合法的字符”(注释以外的)就直接返回这个字符,如果是注释内的字符就递归调用本身——也就是说一直调用它本身直到遇到“合法”字符为止。后来才发现,这样效率太低了——过一会再说这个问题。毕竟,要先make it work, make it right,才能make it efficient。
这里先埋一个伏笔:我使用了标准库istream对象的一个成员函数:putback( char ),用这个函数的作用在于当我检测/*和//时候,如果/后面跟的不是*或者/,我就要把这个字符放回输入流并返回前面的那个/,以免把不是注释的这个字符给漏掉了。按理说,在检测*/的时候也应该这样做,但我开始武断地认为就算漏掉了也没关系——它反正是要被“丢弃”的字符。这就是后来困扰我的问题的真正根源。
以下是我第一个版本的程序,以上的分析过程花了我半个小时,下面的coding也是半个小时:
//LiuKai @ HUST
//2006-7-26
/**********************************************************************************
KillComment4.cpp用来剥掉C++源程序中的所有注释——exec6.6.22
本程序读入一个cpp文件,并将剥去注释的程序写入另一个文件
***********************************************************************************/
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
ifstream is;
ofstream os;
string inFileName = "comment.cpp";
string outFileName = "comment.txt";
enum Flag{ NORMAL, INSTRING, INOLDCOMMENT, INNEWCOMMENT };
Flag status = NORMAL;
char NextValidChar( void )
{
char next = 0;
is.get(next);
if( next == 0 )
return 0;
switch( status )
{
case NORMAL:
{
if( next == '/' )
{
char after;
is.get(after);
if( after == '*' )
{
status = INOLDCOMMENT;
return NextValidChar();
}
else if( after == '/' )
{
status = INNEWCOMMENT;
return NextValidChar();
}
else
{
is.putback(after);
return next;
}
}//end condition /
else if( next == '/"' )
{
status = INSTRING;
return next;
}//end condition "
else
{
status = NORMAL;
return next;
}//other conditions
}
break;
case INSTRING:
{
if( next == '/"' )
{
status = NORMAL;
}
return next;
}
break;
case INOLDCOMMENT:
{
if( next == '*' )
{
char after;
is.get(after);
if( after == '/' )
{
status = NORMAL;
return NextValidChar();
}
}//end condition *
return NextValidChar();
}
break;
case INNEWCOMMENT:
{
string rubbish;
getline(is,rubbish);
if( *(rubbish.end()-1)=='//' )
{
getline(is,rubbish);
}
status = NORMAL;
return '/n';
}
break;
default:
cerr << "Impossible!" << endl;
return 0;
}
}
int main( void )
{
is.open( inFileName.c_str() );
os.open( outFileName.c_str() );
char ch;
ch = NextValidChar();
while( ch )
{
os << ch;
ch = NextValidChar();
}
is.close();
os.close();
return 0;
}
客观的说,这个程序写得是很丑的——有一个超长的函数,就是那个NextValidChar(),coding过程中没有碰到任何语法上的问题(那是当然),第一次测试找了一个不久之前写的小程序,把它读入以后又生成了一个txt文件。很幸运(或者说很不幸)我的程序这次工作得很好,把那个源程序里的注释剥得干干净净,任何情况都符合我的预期,而且代码的格式还保证得很漂亮。我当时的感觉超high,因为我几乎没有遇到任何错误就解决了这个问题——高高兴兴吃饭去了。
晚饭回来还又“欣赏”了一遍这个程序,觉得应该多测试几个文件——我是对的。把读入文件名改成另一个源文件,我立马傻了眼:生成的文件是空的!难道是没读进去?果然,文件名打错了。我松了一口气,改正文件名,再运行一次。怪了,怎么生成的文件里面还是什么都没有?仔细察看生成文件的属性,发现不是空的了,而是六个字节。这就说明文件是读入了,但是剥得有问题。
按照我的注释风格,文件一开头就是这样的:
//LiuKai @ HUST
//2006-7-26
/**********************************************************************************
KillComment.cpp用来剥掉C++源程序中的所有注释——exec6.6.22
本程序读入一个cpp文件,并将剥去注释的程序写入另一个文件
***********************************************************************************/
我用跟踪调试,发现程序在按照我的意思把前两个新式注释替换成回车以后,在读下面那个老式注释的时候返回了一个0,当然是递归返回的,我只能判断问题出在那个老式注释之后。为了证实这个想法,我在第三行添加了一些文字,再次运行后那些文字被原样写到了目标文件,后面的依然是空白。
我测试的两个文件的注释风格完全一样。为什么第一个可以工作得很好但第二个就会出问题呢??我比较了这两个测试文件的注释,突然想到:是不是汉字的问题?我的程序是一个字节一个字节的读进的,碰到0就结束。而汉字占两个字节,会不会有一个字节是0呢?至于第一个文件可以工作也很好解释,那就是没有碰到这样的汉字。
怎么解决这个问题呢?也就是说,怎样区分文件结束标志的那个0和汉字里可能有的那个0呢?呵呵,难不倒我。汉字里即时有字节0,也不可能连着两个字节都是零,当然极端一点可能前一个汉字的低字节和后一个汉字的高字节都是0,那么,总不至于有三个零连在一起的情况吧!而文件结束不一样,文件结束以后,无论对ifstream调用多少次get函数得到的结果一定都是0。这样的话,我就可以在函数判断到该返回0的时候加一个判断,向后在读两个字符,设置一个叫chinese的bool标志。如果都为零,则说明是文件结束,不是汉字的原因,chinese就为false,反之,chinese为true。然后在main函数的循环条件里加上这个东西,也就是说返回的字符是0而且chinese是false时才能退出循环(才是真正的文件结尾)。
改过以后我又测试了一遍,结果和第一个版本的结果完全一样——清者自清,浊者自浊。似乎看不到任何区别。是我的汉字判断有问题,还是问题出在别处?为了弄清到底是不是这个问题,我把测试失败的那个源文件拷贝了一个副本,并把副本里的注释符号手工去掉,只剩汉字。再次测试,发现我两个版本的程序对于副本里的汉字都能顺利地读出来。这时候,我就确定我的错误和汉字无关。
和汉字无关,那就一定和老式注释符/**/有关。回到第一个版本。我耐心察看函数的调用过程,有了一点发现:我的函数在遇到注释里的字符的时候,在跳过这个字符之外还要递归调用自身,当找到一个“合法”字符时再逐级传上来——这是很危险的:注释长度动辄几百个字符,我这样岂不是让函数递归几百层么?且不说效率极低,还有堆栈溢出的可能。会不会就是因为堆栈溢出而导致的程序结束,而只能把溢出之前的东西写进文件呢?很有可能。调用方式必须改。把函数从递归改为非递归,看起来是一件伤筋动骨的事情。可这个函数比较特殊,我所用的“递归”只是为了写程序方便,这个过程从逻辑上来说是不递归的。我只要先设置一个bool量skip,用来描述当前字符是否需要写入目标文件(即前面所说的“合法”),然后让NextValidChar()函数中所有递归调用(即return NextValidChar())的地方都把这个布尔量设为true(即需要skip),然后简单的返回当前值。在向目标文件写字符式需要检查这个量。这样,我就成功地把函数的调用方式改了过来。
这是第三个版本了。但令我沮丧的是,它竟然还和第一个版本得到完全一样的结果。当然我也不后悔我的改动,毕竟它能和前两个版本工作得一样好(对有些源文件结果是正确的),而且效率肯定很高,又算是歪打正着解决了一个潜在的问题吧。
那么真正的问题到底在哪里呢?当我翻动程序看到前面那个putback函数时,终于意识到问题的所在。我在扫描老式注释的结束符*/的时候,没有用skip。我最初是这样想的,*后面如果是/的话,那么它就是结束符,如果不是的话,也用不着把提前读到的*放回输入流,因为这些都是注释里面的字符,迟早是要被丢掉的。这种想法在通常的“*/”情况下是没有问题的,在我经常使用的“***********/”情况下当“*”为奇数个的时候也是没有问题的。问题就出在当星号为偶数个的时候。这时,函数会“正常地”读到倒数第二个星号处,然后向后探索看是否遇到“/”,结果不是/而是最后的那个星号,也就是读到了“**”。由于我没有把多读的那个星号放回输入流,下次读到的就是那个单独的/,而且在以后的字符里,再也没有“*/”出现(这种注释我一般只放在最前面),于是,整个文件都被当成了注释的内容,因而全部被skip了。
真正的解决问题只需要加一条语句,但解决这个问题我却走了这么多弯路。但这些弯路都没白走,它们的确促使我更深入地思考这个程序并且改进其中不合理之处。以下是我最后版本的程序:
//LiuKai @ HUST
//2006-7-26
/**********************************************************************************
KillComment.cpp用来剥掉C++源程序中的所有注释——exec6.6.22
本程序读入一个cpp文件,并将剥去注释的程序写入另一个文件
**********************************************************************
af81
*************/
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
ifstream is;
ofstream os;
string inFileName = "A_IConvert.cpp";
string outFileName = "comment.cpp";
enum Flag{ NORMAL, INSTRING, INOLDCOMMENT, INNEWCOMMENT };
Flag status = NORMAL;
bool skip = false;
bool chinese = false;
char NextValidChar( void )
{
char next = 0;
is.get(next);
if( next == 0 )
{
//处理中文字符
char testc1,testc2;
is.get(testc1);
is.get(testc2);
if( testc1!=0 || testc2!= 0 )
{
chinese = true;
is.putback(testc2);
is.putback(testc1);
}
return 0;
}
switch( status )
{
case NORMAL:
{
if( next == '/' )
{
char after;
is.get(after);
if( after == '*' )
{
status = INOLDCOMMENT;
skip = true;
return next;
}
else if( after == '/' )
{
status = INNEWCOMMENT;
skip = true;
return next;
}
else
{
is.putback(after);
skip = false;
return next;
}
}//end condition /
else if( next == '/"' )
{
status = INSTRING;
skip = false;
return next;
}//end condition "
else
{
status = NORMAL;
skip = false;
return next;
}//other conditions
}
break;
case INSTRING:
{
if( next == '/"' )
{
status = NORMAL;
}
skip = false;
return next;
}
break;
case INOLDCOMMENT:
{
if( next == '*' )
{
char after;
is.get(after);
if( after == '/' )
{
status = NORMAL;
skip = true;
return next;
}
else
{
is.putback(after);
}
}//end condition *
skip = true;
return next;
}
break;
case INNEWCOMMENT:
{
string rubbish;
getline(is,rubbish);
if( *(rubbish.end()-1)=='//' )
{
getline(is,rubbish);
}
status = NORMAL;
skip = false;
return '/n';
}
break;
default:
cerr << "Impossible!" << endl;
return 0;
}
}
int main( void )
{
is.open( inFileName.c_str() );
os.open( outFileName.c_str() );
char ch;
ch = NextValidChar();
while( ch && !chinese )
{
if(!skip)
os << ch;
ch = NextValidChar();
}
is.close();
os.close();
return 0;
}
相关文章推荐
- 程序过程流 调试信息代码 --1
- 终于不用为了调试一个ARX(.net)程序而反复重新启动AutoCAD了!!!
- 集成开发环境VS 程序调试时逐语句与逐过程的区别
- godebug:一个跨平台的Go程序调试工具
- 一个简单程序的汇编执行过程分析
- 调试Python程序时不要直接print一个很大的list
- 一个Linux程序的执行过程的详解
- SDK学习笔记2-一个Win32窗口程序实现过程
- 一个求累加和程序的求解过程
- (迟到的博客,始终是一个过程)Linux/Unix环境下计算C程序运行时间
- 一个java程序的执行过程
- 举例说明一个 java程序的加载,初始化以及运行过程
- 一个程序的编译过程
- Hadoop 调试第一个mapreduce程序过程详细记录总结以及权限问题 Permission denied: user=dr.who
- [ZT]一个microsoft的.exe程序的启动过程
- [Java 09 多线程] 线程是指一个进程在执行过程中可以产生更小的程序单元
- 51Keil C51程序调试过程
- 清理C盘的一个新发现,Visio Studio在调试过程中产生的垃圾文件
- 一个简单的WCF程序创建过程