2011年1月7日星期五

输出从1到1000的数


本文来自: http://stackoverflow.com/q/4568645/89806
有这样一个面试题——请把从1到1000的数打印出来,但你不能使用任何的循环语句或是条件语句。更不能写1000个printf或是cout。用C/C++语言。
我相信,大多数人一开始你可能想到的是递归算法:
void f(int n){
printf("%d\n",n);
(1000-n) ? f(n+1) : exit(0) ;
}
int main(){
f(1);
}
当然,题目中说了不能使用条件语句,所以,上面那种解法的不符合题意的,因为还是变向地使用了条件表达式。不过,我们可以用别的方法来让这个递归终止,比如:
除以零,当程序crash,呵呵。
void f(int n){
printf("%d\n",n);
n/(1000-n);
f(n+1);
}
还有这样退出递归的:

void yesprint(int i);
void noprint(int i);

typedef void(*fnPtr)(int);
fnPtr dispatch[] = { yesprint, noprint };

void yesprint(int i) {
printf("%d\n", i);
dispatch[i / 1000](i + 1);
}

void noprint(int i) { /* do nothing. */ }

int main() {
yesprint(1);
}
还有下面这些各种各样的解法:
#include

/* prints number i */
void print1(int i) {
printf("%d\n",i);
}

/* prints 10 numbers starting from i */
void print10(int i) {
print1(i);
print1(i+1);
print1(i+2);
print1(i+3);
print1(i+4);
print1(i+5);
print1(i+6);
print1(i+7);
print1(i+8);
print1(i+9);
}

/* prints 100 numbers starting from i */
void print100(int i) {
print10(i);
print10(i+10);
print10(i+20);
print10(i+30);
print10(i+40);
print10(i+50);
print10(i+60);
print10(i+70);
print10(i+80);
print10(i+90);
}

/* prints 1000 numbers starting from i */
void print1000(int i) {
print100(i);
print100(i+100);
print100(i+200);
print100(i+300);
print100(i+400);
print100(i+500);
print100(i+600);
print100(i+700);
print100(i+800);
print100(i+900);
}

int main() {
print1000(1);
return 0;
}
不过,print用得多了一些。我们可以用宏嘛。
#include
#define Out(i) printf("%d\n", i++);
#define REP(N) N N N N N N N N N N
#define Out1000(i) REP(REP(REP(Out(i))));
void main()
{
int i = 1;
Out1000(i);
}
不过,我们应该使用C++的一些特性,比如:
使用构造函数
class Printer
{
public:
Printer() { static unsigned i=1; cout << i++ << endl;; }

};

int main()
{
Printer p[1000];
}
或是更为NB的Template:
template
struct NumberGeneration{
static void out(std::ostream& os)
{
NumberGeneration::out(os);
os << N << std::endl;
}
};

template<>
struct NumberGeneration<1>{
static void out(std::ostream& os)
{
os << 1 << std::endl;
}
};

int main(){
NumberGeneration<1000>::out(std::cout);
}
最后来个BT一点的:
void main(int j) {
printf("%d\n", j);
(main + (exit - main)*(j/1000))(j+1);
}

2010年12月13日星期一

标准输出重定向

标准输入输出指的是控制台的输入输出,涉及的函数和对象包括C运行库中的printf,scanf,fprintf……,C++标准库中的cin,cout,cerr,……
这里只讨论标准输出的重定向。重定向包括定向到文件、屏幕(Console窗口)、GUI中。
(1)重定向到文件中
利用freopen函数实现。当将C运行库的stdout重定向到文件中后,C++标准库中的cout未做任何改变即已重定向了。
//将stdout重定向到文件中
FILE *stream;
if( (stream = freopen("file.txt", "w", stdout ) ) == NULL )
    exit(-1);
printf("this is stdout output");

//重新将stdout 重定向到console中
stream = freopen("CON","w",stdout);
pintf("And now back to the console once again");

(2)重定向到屏幕
参考资料:http://dslweb.nwnexus.com/~ast/dload/guicon.htm
对于console程序而言,谈论重定向到屏幕可能没有意义。所以这里谈论的是,对于GUI程序,也就是普通的Windows程序中,如何使用printf输出到console。
主要思路是,AllocConsole分配一个控制台窗口,GetStdHandle获得相应句柄,再调用_open_osfhandle关联到C运行库的句柄,_fdopen打开句柄获得文件流指针,与stdout关联。
/*****************************************************
*guicon.cpp
*
*****************************************************/

#include <windows.h>
#include <io.h>
#include <stdio.h>
#include <fcntl.h>

#include "guicon.h"

static const WORD MAX_CONSOLE_LINES = 500;

void RedirectIOToConsole()
{
int hConHandle;
//HANDLE lStdHandle;
FILE *fp;
CONSOLE_SCREEN_BUFFER_INFO coninfo;

// allocate a console for this app
AllocConsole();

// set the screen buffer to be big enough to let us scroll text
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE),
&coninfo);
coninfo.dwSize.Y = MAX_CONSOLE_LINES;
SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),
coninfo.dwSize);

// redirect unbuffered STDOUT to the console
hConHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT);
fp = _fdopen( hConHandle, "w" );
*stdout = *fp;
setvbuf( stdout, NULL, _IONBF, 0 );

// redirect unbuffered STDIN to the console
hConHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_INPUT_HANDLE), _O_TEXT);
fp = _fdopen( hConHandle, "r" );
*stdin = *fp;
setvbuf( stdin, NULL, _IONBF, 0 );

// redirect unbuffered STDERR to the console
hConHandle = _open_osfhandle((intptr_t)GetStdHandle(STD_ERROR_HANDLE), _O_TEXT);
fp = _fdopen( hConHandle, "w" );
*stderr = *fp;
setvbuf( stderr, NULL, _IONBF, 0 );

}

(3)重定向到GUI中
参考资料:http://blog.vckbase.com/michael/archive/2005/06/06/6163.aspx

将stdout重定向某个MFC程序View中显示出来,实现稍有点复杂。总体思路是
1)创建一个匿名管道(pipe)
2)将stdout重定向到这个pipe上
3)创建一个线程,读取pipe中的内容,然后将这些内容输出到你希望输出的地方,譬如一个view里。
启动进程时,先调用StdoutRedirect(),那么这个进程中所有的printf都可以输出到View里。

#include <windows.h>
#include <io.h>
#include <stdio.h>
#include <fcntl.h>

HANDLE hOutputReadTmp = 0;
HANDLE hOutputWrite = 0;
void DisplayError(char* szMsg)
{
 TRACE(szMsg);
 TRACE(" ");
}

DWORD WINAPI ReadStdout(LPVOID lpvThreadParam)
{
 CHAR lpBuffer[256];
 DWORD nBytesRead;

 while(TRUE)
 {
 if (!ReadFile(hOutputReadTmp, lpBuffer,sizeof(lpBuffer),
 &nBytesRead,NULL) || !nBytesRead)
 {
 if (GetLastError() == ERROR_BROKEN_PIPE)
 break; // pipe done - normal exit path.
 else
 DisplayError("ReadFile"); // Something bad happened.
 }

 if (nBytesRead >0 )
 {
 //获取到printf的输出
 lpBuffer[nBytesRead] = 0;

 //replace your code here. Do it yourself.
 DisplayInView(lpBuffer);
 }
 }

 return 0;
}

void StdoutRedirect()
{
 if (!CreatePipe(&hOutputReadTmp,&hOutputWrite,0,0))
 DisplayError("CreatePipe");

 int hCrt;
 FILE *hf;
 //AllocConsole();
 hCrt = _open_osfhandle(
 (long)hOutputWrite,
 _O_TEXT
 );
 hf = _fdopen( hCrt, "w" );
 *stdout = *hf;
 int i = setvbuf( stdout, NULL, _IONBF, 0 );

 // Launch the thread that gets the input and sends it to the child.
 DWORD ThreadID;
 HANDLE hThread = ::CreateThread(NULL,0,ReadStdout,
 0,0,&ThreadID);
}


后记:
想实现类似此帖中的接口,它把重定向到GUI的操作用一个回调函数来实现,可以很好地扩展,封装了匿名管道的操作,而不是像(3)中那样,用户需要在新创建的线程中修改。但尝试失败,可能是线程的操作不熟悉。

2010年8月2日星期一

一个小函数,ShowErrMsg

void ShowErrMsg()
{
TCHAR szBuf[80];
LPVOID lpMsgBuf;
DWORD dw = GetLastError();

FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPTSTR) &lpMsgBuf,
0, NULL );

MessageBox(NULL, lpMsgBuf, "系统错误", MB_OK|MB_ICONSTOP);

LocalFree(lpMsgBuf);
}

[转]关于Windows平台下内存的分配和释放

Windows平台下主要的内存管理途径:
new / delete
malloc / free
CoTaskMemAlloc / CoTaskMemFree
IMalloc::alloc / IMalloc/free
GlobalAlloc / GlobalFree
LocalAlloc / LocalFree
HeapAlloc / HeapFree
VirtualAlloc / VirtualFree

VirtualAlloc以页面为单位(4K)进行分配,是操纵虚拟内存的底层函数。

HeapAlloc在指定堆上分配内存。
Windows为每个进程设置了一个缺省堆,应用程序也可以用HeapCreate创建更多的堆。

GlobalAlloc和LocalAlloc原本为16位环境下的API,Win32为了保持兼容性而保留了这两个函数,但在Win32下已不存在全局堆(所有的堆都是进程私有),所以 GlobalAlloc 和 LocalAlloc 在Win32下意义完全相同,它们都在进程缺省堆中进行内存分配。需注意的是,在Win32下,GlobalAlloc的字面意思已失效,它并不能在进程间共享数据。微软强调,GlobalAlloc/LocalAlloc 比 HeapAlloc 要慢,已不再推荐使用,但由于GlobalAlloc/LocalAlloc 具有简单的使用接口,所以即使在微软所提供的源码中,它们仍被大量使用。

malloc是CRT函数,实现方式取决于具体的CRT版本。VC++的malloc系对 HeapAlloc 作简单的包装,而Borland C++则选择自己实现malloc。在应用程序启动时,CRT创建自己的私有堆,驻留在Win32堆的顶部。

C++中的new先调用 operator new,再调用构造函数生成类对象。而 operator new 在缺省情况下调用 malloc 进行内存分配。应用程序可以重载 operator new,选择其他的内存分配方式。

CoTaskMemAlloc用于COM对象,它在进程的缺省堆中分配内存。
IMalloc接口是对 CoTaskMemAlloc/CoTaskMemFree 的再次封装。

调用关系:
msvcrt.malloc => kernel32.HeapAlloc(ntdll.RtlAllocateHeap)
kernel32.LocalAlloc => ntdll.RtlAllocateHeap
kernel32.GlobleAlloc => ntdll.RtlAllocateHeap
kernel32.HeapAlloc == ntdll.RtlAllocateHeap (映射)
kernel32.VirtualAlloc => kernel32.VirtualAllocEx
kernel32.VirtualAllocEx => ntdll.NtAllocateVirtualMemory
ntdll.RtlAllocateHeap => ntdll.NtAllocateVirtualMemory
ntdll.NtAllocateVirtualMemory => ntdll.KiFastSystemCall
ntdll.KiFastSystemCall => sysenter指令 (0F34)

即:
new -> malloc -> HeapAlloc -> VirtualAlloc -> 驱动程序的_PageAlloc

2010年4月21日星期三

简易垃圾回收器

一、垃圾回收器概述

典型的垃圾回收器算法有三种:一是引用计数(Reference Counting),二是标记并清除(Mark-Sweep),三是复制(Copying)。对于这三种算法,可以用餐巾纸的例子来通俗解释。

引用计数( Reference Counting )算法

拿餐巾纸的例子来说,这种算法的原理大致可以描述为:

午餐时,为了把脑子里突然跳出来的设计灵感记下来,我从餐巾纸袋中抽出一张餐巾纸,打算在上面画出系统架构的蓝图。按照“餐巾纸使用规约之引用计数版”的要求,画图之前,我必须先在餐巾纸的一角写上计数值 1,以表示我在使用这张餐巾纸。这时,如果你也想看看我画的蓝图,那你就要把餐巾纸上的计数值加1,将它改为 2,这表明目前有2个人在同时使用这张餐巾纸(当然,我是不会允许你用这张餐巾纸来擦鼻涕的)。你看完后,必须把计数值减1,表明你对该餐巾纸的使用已经结束。同样,当我将餐巾纸上的内容全部誊写到笔记本上之后,我也会自觉地把餐巾纸上的计数值减1。此时,不出意外的话,这张餐巾纸上的计数值应当是0,它会被垃圾收集器——假设那是一个专门负责打扫卫生的机器人——捡起来扔到垃圾箱里,因为垃圾收集器的惟一使命就是找到所有计数值为 0 的餐巾纸并清理它们。

引用计数算法的优点和缺陷同样明显。这一算法在执行垃圾收集任务时速度较快,但算法对程序中每一次内存分配和指针操作提出了额外的要求(增加或减少内存块的引用计数)。更重要的是,引用计数算法无法正确释放循环引用的内存块,这说明,单是使用引用计数算法还不足以解决垃圾收集中的所有问题。当然,作为一种最简单、最直观的解决方案,引用计数算法本身具有其不可替代的优越性。

标记-清除( Mark-Sweep )算法

第一种实用和完善的垃圾收集算法是 J. McCarthy 等人在 1960 年提出并成功地应用于 Lisp 语言的标记-清除算法。仍以餐巾纸为例,标记-清除算法的执行过程是这样的:

午餐过程中,餐厅里的所有人都根据自己的需要取用餐巾纸。当垃圾收集机器人想收集废旧餐巾纸的时候,它会让所有用餐的人先停下来,然后,依次询问餐厅里的每一个人:“你正在用餐巾纸吗?你用的是哪一张餐巾纸?”机器人根据每个人的回答将人们正在使用的餐巾纸画上记号。询问过程结束后,机器人在餐厅里寻找所有散落在餐桌上且没有记号的餐巾纸(这些显然都是用过的废旧餐巾纸),把它们统统扔到垃圾箱里。

正如其名称所暗示的那样,标记-清除算法的执行过程分为“标记”和“清除”两大阶段。这种分步执行的思路奠定了现代垃圾收集算法的思想基础。与引用计数算法不同的是,标记-清除算法不需要运行环境监测每一次内存分配和指针操作,而只要在“标记”阶段中跟踪每一个指针变量的指向——用类似思路实现的垃圾收集器也常被后人统称为跟踪收集器( Tracing Collector )


复制( Copying )算法

为了解决标记-清除算法在垃圾收集效率方面的缺陷, M. L. Minsky 于 1963 年发表了著名的论文“一种使用双存储区的 Lisp 语言垃圾收集器( A LISP Garbage Collector Algorithm Using Serial Secondary Storage )”。 M. L. Minsky 在该论文中描述的算法被人们称为复制算法,它也被 M. L. Minsky 本人成功地引入到了 Lisp 语言的一个实现版本中。

复制算法别出心裁地将堆空间一分为二,并使用简单的复制操作来完成垃圾收集工作,这个思路相当有趣。借用餐巾纸的比喻,我们可以这样理解 M. L. Minsky 的复制算法:

餐厅被垃圾收集机器人分成南区和北区两个大小完全相同的部分。午餐时,所有人都先在南区用餐(因为空间有限,用餐人数自然也将减少一半),用餐时可以随意使用餐巾纸。当垃圾收集机器人认为有必要回收废旧餐巾纸时,它会要求所有用餐者以最快的速度从南区转移到北区,同时随身携带自己正在使用的餐巾纸。等所有人都转移到北区之后,垃圾收集机器人只要简单地把南区中所有散落的餐巾纸扔进垃圾箱就算完成任务了。下一次垃圾收集的工作过程也大致类似,惟一的不同只是人们的转移方向变成了从北区到南区。如此循环往复,每次垃圾收集都只需简单地转移(也就是复制)一次,垃圾收集速度无与伦比——当然,对于用餐者往返奔波于南北两区之间的辛劳,垃圾收集机器人是决不会流露出丝毫怜悯的。

M.L.Minsky的发明绝对算得上一种奇思妙想。分区、复制的思路不仅大幅提高了垃圾收集的效率,而且也将原本繁纷复杂的内存分配算法变得前所未有地简明和扼要(既然每次内存回收都是对整个半区的回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存就可以了),这简直是个奇迹!不过,任何奇迹的出现都有一定的代价,在垃圾收集技术中,复制算法提高效率的代价是人为地将可用内存缩小了一半。实话实说,这个代价未免也太高了一些。

二、基于引用计数的垃圾回收器

为了实现引用计数的垃圾回收器,必须有某种方法来跟踪指向每块动态分配内存的指针的数量。解决方案是:可以建立一个新的支持垃圾回收的指针类型,也就是说该指针类型支持指针的操作,又有个数据成员来跟踪指向分配内存的指针的数量。具体来说,新的指针类型必须完成三件事情:第一,它必须为使用中的动态分配的对象维护一个引用计数的链表。第二,它必须跟踪所有的指针运算符,每次某个指针指向一个对象时,都要使这个对象的引用计数增1,每次某个指针重新指向其他对象时,都要使原本指向的对象的引用计数减1。第三,它必须回收那些引用计数降为0的对象。

根据上述的设计思路,构造指针类GCPtr
template
class GCPtr
{
private:

static list > gclist; //记录分配的每块内存的引用计数
T * addr; // 当前存储指针
bool isArray;
unsigned arraySize;
static bool first; // true when first GCPtr is created,用来注册退出函数,以解决引用计数实现的垃圾回收的循环引用问题
typename list >::iterator findPtrInfo(T * ptr); //查找ptr是否已记录在gclist链表中
public:

//构造函数,复制构造函数,析构函数及指针操作的一些函数
//特别的,有几个static函数,是垃圾回收器工作的核心
static bool collect();
static void shutdown(); //atexit注册的退出函数,用于清理循环引用的对象
};

其中GCInfo设计如下
template
class GCInfo
{
public :
unsigned refcount;
T *memptr;
bool isArray;
unsigned arraySize;
GCInfo(T *mptr,unsigned size = 0)
{
refcount = 1;
memptr = mptr;
if(size != 0)
isArray = true;
else
isArray = false;
arraySize = size;
}
};

在源码中,还提供了另外两个类,Iter 和 OutOfRangeExc。Iter是封装的迭代器类,因为GCPtr虽然支持普通指针的"*"和“->”操作,但并不提供指针运算,比如“++”,“--”。Iter类提供了类型安全的指针运算,使用方法与一般迭代器相同。
OutOfRangeExc是个异常类,但并没有实际的成员。如果有需要用到异常,可通过它进行扩展。

侯捷说“源码之前,了无秘密“,放出源码在此。

2010年3月21日星期日

关闭vmware喇叭报警音

在vmware里面安装linux系统后,操作时经常使用tab键,然后就经常听到pc
speaker突然叫一声,如果正当戴着耳机欣赏音乐,被这么一叫还要吓一跳。下面是解决办法:
C:\Documents and Settings\All Users\Application Data\VMware\VMware
Workstation中config.ini,在里面写入mks.noBeep =
"TRUE",如果没有config.ini文件,自己建立,修改完后把guest os重启下就OK了

2010年3月18日星期四

[转] (i++)+(++i)+(i++),这是个罪恶的语句

源代码是程序员之间的交流,不是程序员之间的互打哑谜。───题记

这是郑晖老师的话,我引为题记。我想,这里的读者一定会有大学生,你们在学C语言时,一定遇到过许多求“(i++)+(++i)+(i++)”之类的问题。昨天有人问我,令我一时语塞,研究了半天。今天我要告诉你们,这样的语句是罪恶的。你们也许要说,这明明是考试重点呀。别急,下面我们分成三个部分来组织:在第一部分中,解释为什么它有罪;第二部分,为了解救你们,我们找出解题规律;在第三部分中,给出几个更变态的题目,今后谁出这种题给你,你就用我这里的题目去问他。


一. 这种语句的合法性

C语言的创始人D.M.R在《CPL》中明确指出:

自增与自减运算符只能作用于变量,类似于表达式(i+j)++是非法的。

自增运算实际上包括了一个赋值运算,而表达式不能作赋值运算的左值,因为它没有确定的内存地址。只要你明白 (i+j)=5 是非法的,那就不难理解为什么(i+j)++非法。以此为依据,(i++)+(i++)这个语句中的后一项,是非法的。尽管看起来它只是一个i++,但别忘了,在前一个i++的作用下,后面的“i”本身就已经是表达式了,表达式不能再做++。一般地,在一个语句中,对同一个变量调用多次自增或自减运算,都是非法的。D.M.R还提醒,编译器应在这种情况下给出警告。事实上,gcc确实会对此给出一个:

Warning: operation on ‘i’ may be undefined

这已经够清楚了,无需多言。请编写教学大纲者注意,多年以来,你们一直在用非法语句作考试重点。

二. 如何计算

尽管它非法,但同学们没有执法权,只能任其蹂躏。所以,我们探索一下,编译器在摸不到头脑的情况下,是如何处理这个语句的。我们以 (i++)+(++i)+(++i)+(i++)+(i++) 为例,先将其编译成汇编代码,结果是这样的:

addl -4(%rbp), %eax movl -4(%rbp), %eax addl -4(%rbp), %eax incl -4(%rbp) addl -4(%rbp), %eax addl -4(%rbp), %eax addl -4(%rbp), %eax incl -4(%rbp) incl -4(%rbp) incl -4(%rbp)为了照顾看不懂汇编的朋友,我把它直接对应着翻译成C语言。用变量sum表示要求的和,就是下面这个样子:

i=i+1; sum=i; sum=sum+i; i=i+1; sum=sum+i; sum=sum+i; sum=sum+i; i=i+1; i=i+1; i=i+1这段代码能帮助你理解这个过程,我就不说了,留给你自己探索。下面直接给出我分析的结果:

1. 先将所有的 i++ 改成 i ,然后在整个语句的最后,统一将 i 自增相应的次数(语句中有几个 i++,就在最后自增几次)。我们的例子,这时会变成 i+(++i)+(++i)+i+i; i++; i++; i++;
2. 按照加法的结合性,先将左起前两项相加。如果前两项中含有 ++i,则先算 ++i;
3. 前两项的和作为一项,与第三项相加,以此类推。同样,遇到 ++i,就先算 ++i。

以上就是编译器处理这种语句的规律,当然,这并不是C语言定义的,只是编译器在出错情况下的无奈之举。大家可以用这个方法来解题,下面我就回答昨天一位同学问我的问题:

i=5; 求 (i++)+(++i)+(i++) 的值

按上面的方法做:
1. 先把i++换到最后面,变成 i + (++i) + i ; i++; i++;
2. 从左到右累加,先拿出前两项 i + (++i)。先算 ++i,i 的值变成6,两项相加得12;
3、计算 12 +i,得18。