[Win7 vs2010]
Q: C库和系统api之间是什么关系?
A: 如下图,简单示意:
可以看出,C库一部分是使用系统api实现自身功能(比如文件操作),另一部分并不会直接依赖系统api,单独实现功能(比如字符串处理)。另外,对于驱动模块,按照不同的理解,也可以放入操作系统内部或者操作系统下层;如果把操作系统看成隐形的CPU和内存的驱动,那么它也可以看成和常规意义的硬件驱动是平级的。而,C库,从理论上来说,没必要和驱动有依赖关系。当然,访问操作系统的方式不仅仅是它提供的api,也是可以通过其它方式来访问。
Q:c库和多线程到底什么关系?
A: 多线程在操作系统上的运用导致了很多库,包括之前的单线程版本的c库,也必须做出相应修改,才能保证运行不会出现问题。例如,如下是vs2010附带的fgets.c中部分源代码:
_TSCHAR * __cdecl _fgetts ( _TSCHAR *string, int count, FILE *str ) { REG1 FILE *stream; REG2 _TSCHAR *pointer = string; _TSCHAR *retval = string; int ch; _VALIDATE_RETURN(( string != NULL ) || ( count == 0 ), EINVAL, NULL); _VALIDATE_RETURN(( count >= 0 ), EINVAL, NULL); _VALIDATE_RETURN(( str != NULL ), EINVAL, NULL); if (count == 0) { return NULL; } /* The C Standard states the input buffer should remain unchanged if EOF is encountered immediately. Hence we do not blank out the input buffer here */ /* Init stream pointer */ stream = str; _lock_str(stream); __try { #ifndef _UNICODE _VALIDATE_STREAM_ANSI_SETRET(stream, EINVAL, retval, NULL); #endif /* _UNICODE */ if (retval!=NULL) { while (--count) { if ((ch = _fgettc_nolock(stream)) == _TEOF) { if (pointer == string) { retval=NULL; goto done; } break; } if ((*pointer++ = (_TSCHAR)ch) == _T('\n')) break; } *pointer = _T('\0'); } /* Common return */ done: ; } __finally { _unlock_str(stream); } return(retval); }可以看出,它的调用过程中会先调用_lock_str:
#define _lock_str(s) _lock_file(s)_lock_file的内部实现:
void __cdecl _lock_file ( FILE *pf ) { /* * The way the FILE (pointed to by pf) is locked depends on whether * it is part of _iob[] or not */ if ( (pf >= _iob) && (pf <= (&_iob[_IOB_ENTRIES-1])) ) { /* * FILE lies in _iob[] so the lock lies in _locktable[]. */ _lock( _STREAM_LOCKS + (int)(pf - _iob) ); /* We set _IOLOCKED to indicate we locked the stream */ pf->_flag |= _IOLOCKED; } else /* * Not part of _iob[]. Therefore, *pf is a _FILEX and the * lock field of the struct is an initialized critical * section. */ EnterCriticalSection( &(((_FILEX *)pf)->lock) ); }
对于_lock函数:
void __cdecl _lock ( int locknum ) { /* * Create/open the lock, if necessary */ if ( _locktable[locknum].lock == NULL ) { if ( !_mtinitlocknum(locknum) ) _amsg_exit( _RT_LOCK ); } /* * Enter the critical section. */ EnterCriticalSection( _locktable[locknum].lock ); }
Q: 那么如何设置将使用单线程版本的c库或者多线程版本的c库?
A: 如下图,是vs2010设置使用多线程版本c库的截图:
按照微软的说法,从vs2005开始,单线程版本的C库就已经被移除,所以可以不用担心使用单线程版本C库导致问题了。如果使用的是VC6,依然可以设置使用单线程版本C库。如果使用IDE工具没找到,可以使用命令行工具寻找相关选项:
当然,grep需要cygwin的支持。
Q: printf函数,它可以处理变参,内部会如何处理呢?
A: 参数入栈的原则很好地支撑了变参的处理。也就是说,当确定了参数中一个的地址,那么其他参数的地址是随着这个地址按照类型大小变动即可。如下:
#define va_start _crt_va_start #define va_end _crt_va_end
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) ) #define _crt_va_end(ap) ( ap = (va_list)0 )可以看出,va_start也就是获取了变参的首地址,而va_end也就是将操作变参的数据设置为0,来结束处理。当然,上面的宏定义是一种平台下特殊情况,不同平台下的定义会有所不同。顺便将_ADDRESSOF宏和_INTSIZEOF宏列出:
#ifdef __cplusplus #define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) ) #else #define _ADDRESSOF(v) ( &(v) ) #endif
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )上面的这个宏表示以sizeof(int)对齐的大小。
Q: printf函数内部究竟调用了什么操作系统API?
A: 从表面分析,应该会调用系统控制台输出的API; 如下是用ida工具对于如下代码hello.c生成的可执行文件的分析:
#include <stdio.h> #include <wchar.h> int main() { char ch = 'a'; wchar_t wch = (wchar_t)ch; printf("%C\n", wch); return 0; }使用cl hello.c编译成hello.exe.
可以确切看到内部调用的系统API名称。当然,使用hook WriteConsoleW函数的方式同样可以得出结论。
Q: 经常看到关于文件输入输出的符号stdin, stdout, stderr,它们究竟是什么?
A: 它们是FILE *类型的变量。如下定义:
#define stdin (&__iob_func()[0]) #define stdout (&__iob_func()[1]) #define stderr (&__iob_func()[2])__iob_func定义如下:
/* * FILE descriptors; preset for stdin/out/err (note that the __tmpnum field * is not initialized) */ FILE _iob[_IOB_ENTRIES] = { /* _ptr, _cnt, _base, _flag, _file, _charbuf, _bufsiz */ /* stdin (_iob[0]) */ { _bufin, 0, _bufin, _IOREAD | _IOYOURBUF, 0, 0, _INTERNAL_BUFSIZ }, /* stdout (_iob[1]) */ { NULL, 0, NULL, _IOWRT, 1, 0, 0 }, /* stderr (_iob[3]) */ { NULL, 0, NULL, _IOWRT, 2, 0, 0 }, }; /* These functions are for enabling STATIC_CPPLIB functionality */ _CRTIMP FILE * __cdecl __iob_func(void) { return _iob; }不同平台,甚至相同平台下不同环境,对于描述符、句柄、指针之类的名称含义理解不尽一致。所以,这里使用英文的方式来说明。FILE *是C语言抽象出来的文件操作指针,而对于FILE结构内部的_file成员可以被看成是handle,它是整形数据,正如stdin对应于file handle 0, stdout对应于file handle 1,stderr对应于file handle 2.
对于FILE *和file handle, 可以使用fileno和fdopen来互相获取对应的数值。
另外,上面的代码是从_file.c头文件中摘录,stderr (_iob[3])应该是它的错误,应该为stderr (_iob[2]).
Q: 对于字符串处理函数strtok, 它是如何保存中间状态的?
A: 单线程版本的strtok函数,可以通过static变量保存中间处理的位置信息,使得后来的调用可以继续工作。对于多线程版本的strtok函数,这样就不行了。实际上,它是采用了TLS的方式来保存这些中间数据。如下:
struct _tiddata { unsigned long _tid; /* thread ID */ uintptr_t _thandle; /* thread handle */ int _terrno; /* errno value */ unsigned long _tdoserrno; /* _doserrno value */ unsigned int _fpds; /* Floating Point data segment */ unsigned long _holdrand; /* rand() seed value */ char * _token; /* ptr to strtok() token */ wchar_t * _wtoken; /* ptr to wcstok() token */ unsigned char * _mtoken; /* ptr to _mbstok() token */ /* following pointers get malloc'd at runtime */ char * _errmsg; /* ptr to strerror()/_strerror() buff */ wchar_t * _werrmsg; /* ptr to _wcserror()/__wcserror() buff */ char * _namebuf0; /* ptr to tmpnam() buffer */ wchar_t * _wnamebuf0; /* ptr to _wtmpnam() buffer */ char * _namebuf1; /* ptr to tmpfile() buffer */ wchar_t * _wnamebuf1; /* ptr to _wtmpfile() buffer */ char * _asctimebuf; /* ptr to asctime() buffer */ wchar_t * _wasctimebuf; /* ptr to _wasctime() buffer */ void * _gmtimebuf; /* ptr to gmtime() structure */ char * _cvtbuf; /* ptr to ecvt()/fcvt buffer */ unsigned char _con_ch_buf[MB_LEN_MAX]; /* ptr to putch() buffer */ unsigned short _ch_buf_used; /* if the _con_ch_buf is used */ /* following fields are needed by _beginthread code */ void * _initaddr; /* initial user thread address */ void * _initarg; /* initial user thread argument */ /* following three fields are needed to support signal handling and * runtime errors */ void * _pxcptacttab; /* ptr to exception-action table */ void * _tpxcptinfoptrs; /* ptr to exception info pointers */ int _tfpecode; /* float point exception code */ /* pointer to the copy of the multibyte character information used by * the thread */ pthreadmbcinfo ptmbcinfo; /* pointer to the copy of the locale informaton used by the thead */ pthreadlocinfo ptlocinfo; int _ownlocale; /* if 1, this thread owns its own locale */ /* following field is needed by NLG routines */ unsigned long _NLG_dwCode; /* * Per-Thread data needed by C++ Exception Handling */ void * _terminate; /* terminate() routine */ void * _unexpected; /* unexpected() routine */ void * _translator; /* S.E. translator */ void * _purecall; /* called when pure virtual happens */ void * _curexception; /* current exception */ void * _curcontext; /* current exception context */ int _ProcessingThrow; /* for uncaught_exception */ void * _curexcspec; /* for handling exceptions thrown from std::unexpected */ #if defined (_M_IA64) || defined (_M_AMD64) void * _pExitContext; void * _pUnwindContext; void * _pFrameInfoChain; unsigned __int64 _ImageBase; #if defined (_M_IA64) unsigned __int64 _TargetGp; #endif /* defined (_M_IA64) */ unsigned __int64 _ThrowImageBase; void * _pForeignException; #elif defined (_M_IX86) void * _pFrameInfoChain; #endif /* defined (_M_IX86) */ _setloc_struct _setloc_data; void * _reserved1; /* nothing */ void * _reserved2; /* nothing */ void * _reserved3; /* nothing */ #ifdef _M_IX86 void * _reserved4; /* nothing */ void * _reserved5; /* nothing */ #endif /* _M_IX86 */ int _cxxReThrow; /* Set to True if it's a rethrown C++ Exception */ unsigned long __initDomain; /* initial domain used by _beginthread[ex] for managed function */ }; typedef struct _tiddata * _ptiddata;可以看到,里面有strtok函数中间状态需要保存的token指针位置;而,对于strtok的实现代码也能看出:
#ifdef _SECURE_VERSION #define _TOKEN *context #else /* _SECURE_VERSION */ #define _TOKEN ptd->_token #endif /* _SECURE_VERSION */ #ifdef _SECURE_VERSION char * __cdecl strtok_s ( char * string, const char * control, char ** context ) #else /* _SECURE_VERSION */ char * __cdecl strtok ( char * string, const char * control ) #endif /* _SECURE_VERSION */ { unsigned char *str; const unsigned char *ctrl = control; unsigned char map[32]; int count; #ifdef _SECURE_VERSION /* validation section */ _VALIDATE_RETURN(context != NULL, EINVAL, NULL); _VALIDATE_RETURN(string != NULL || *context != NULL, EINVAL, NULL); _VALIDATE_RETURN(control != NULL, EINVAL, NULL); /* no static storage is needed for the secure version */ #else /* _SECURE_VERSION */ _ptiddata ptd = _getptd(); #endif /* _SECURE_VERSION */ /* Clear control map */ for (count = 0; count < 32; count++) map[count] = 0; /* Set bits in delimiter table */ do { map[*ctrl >> 3] |= (1 << (*ctrl & 7)); } while (*ctrl++); /* Initialize str */ /* If string is NULL, set str to the saved * pointer (i.e., continue breaking tokens out of the string * from the last strtok call) */ if (string) str = string; else str = _TOKEN; /* Find beginning of token (skip over leading delimiters). Note that * there is no token iff this loop sets str to point to the terminal * null (*str == '\0') */ while ( (map[*str >> 3] & (1 << (*str & 7))) && *str ) str++; string = str; /* Find the end of the token. If it is not the end of the string, * put a null there. */ for ( ; *str ; str++ ) if ( map[*str >> 3] & (1 << (*str & 7)) ) { *str++ = '\0'; break; } /* Update nextoken (or the corresponding field in the per-thread data * structure */ _TOKEN = str; /* Determine if a token has been found. */ if ( string == str ) return NULL; else return string; }函数的最后, _TOKEN = str; 正是操作了tiddata.
如果希望得到tiddata是如何初始化的,查看_beginthreadex函数的源代码(部分):
/* * Allocate and initialize a per-thread data structure for the to- * be-created thread. */ if ( (ptd = (_ptiddata)_calloc_crt(1, sizeof(struct _tiddata))) == NULL ) goto error_return; /* * Initialize the per-thread data */ _initptd(ptd, _getptd()->ptlocinfo); ptd->_initaddr = (void *) initialcode; ptd->_initarg = argument; ptd->_thandle = (uintptr_t)(-1);
Q: 断言判断的代码应该怎么写?
A: 它的核心就在于如何输出错误信息和结束程序。
#define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
在_wassert中将会组装错误信息并输出,并进入结束程序状态。
xichen
2012-5-29 16:54:53