通常,需要将部分日志信息输出到程序并记录到文件中。 在这种情况下,使用printf对我们来说非常方便。 由于printf可以将信息输出到stdout即控制台画面,所以GUI程序看不到printf的输出结果,但可以将其输出重定向到指定的文件。 也就是说,使用Freopen(「c:yourlog.log”),
通过“a,stdout”或yourapp.exe
c:yourlog.log完成输出重定向操作。
但是,简单的printf无法执行此操作,因为记录日志时通常需要记录更多的信息,例如运行时。 此外,为了防止日志信息以外的丢失,建议每次printf时立即调用fflush。 因此,通常通过以下方式完成日志记录操作:
void _ _ cdecl log0 (const char * _ format,) {charbuff[10240],tm[80];
va_listvl;
VA_start(VL,_Format );
_strtime(TM;
vnprintf(buff,10240,_Format,vl );
printf(%s-%s )、tm和buff );
fflush
(stdout ) va_end(VL;
}
从这段代码中可以看到,您必须预先定义inflexibility,它应该包含足够大的缓存中的所有可能数据。 这就是使用该方法产生的inflexibility。 它有多大才足够大呢? 10240? 102400? 1024000? 恐怕你的堆栈也没这么大吧! 你给堆分配存储空间也一样!
下面介绍了如何在不预先分配缓存的情况下,从当天开始就可以接受任意长度的信息并将其输出到文件中。 当然,只要系统不超过允许的大小,log0就可以完成我们想做的所有事情(输出日志前缀信息等),并将调用方传递给log0的参数“原样”传递给printf,所有的可变参数都可以是的,接下来要解决的是如何将这些可变参数“传递”到printf。
在log0内部,不能使用常用的参数名称将参数传递给printf,因为不知道调用方传递了多少参数。 但是,如果你看看log0的函数声明,他不是和printf的声明完全一致吗? (实际上,如void所示,log0的参数和printf的部分一致即可
也可以是log1(char*filename,int len,char* _Format,)。 )?
也就是说,拥有相同声明的函数在被调用时,他们拥有的参数堆栈,也就是Stack
Frame ) )的结构相同。 因此,只要可以从一个函数a“突然”跳到另一个函数b,b拥有的参数堆栈和a就具有相同的数据。 这意味着他们“共享”同一参数堆栈的数据。 需要注意的是,这里的跳转不能使用普通函数实现。 调用函数时,编译器会在背后做很多事情,例如设置新的ESP指针。 因此,无法达到共享参数堆栈数据的目的。 为了防止编译器在函数调用时在背后执行任何操作,必须使用naked函数。 这样的函数可以自己利用堆栈资源,自己控制一切。 有了这样的函数,就可以简单而有效地实现我们的目的。
voidmkprefix () {charbuff[80];
_strtime(buff );
printf('%s-',buff );
}_declspec(naked ) void__cdecl
xprintf(constchar*_format,) {
__asm{calldwordptr[mkprefix]
popebx
/*函数将返回地址保存到ebx */calldwordptr [ printf ]
subesp,4/*1 */
调用fflush将数据立即保存到文件
*/calldwordptr[__iob_func]
addeax,0x 20推送eax
calldwordptr[fflush]
addesp,4/*2 */movdwordptr[esp],ebx
/*恢复函数返回地址*/ret
}
在代码中,在1和2的地方分别在ESP中减去4再添加4,所以可以完全忽略这两个地方的代码。 在此添加是为了更好地理解函数调用的工作方式(例如
清除)。 您也可以记录在mkprefix中创建的足够复杂的更多信息,然后在log0中将参数传递给mkprefix,如log1所示。 但是,这样处理会有点复杂,为了简单起见,就不说明这个方法了。
当然,这只是所谓的“堆栈共享”技术的一个应用。 我想掌握了这个技术之后,一定会应用到其他更合适的地方。
实际上,VC8提供了可变参数的宏,通过以下简单的调用就可以完成日间记录操作。 信息还很完整。
#definetrace(fmt,) printf('%s ) %s:%d )-'##fmt,mkprefix )、__FILE__、__LINE__,
',234,'xxxxx
();