c库(上)----小话c语言(17)

news/2024/12/29 15:56:38/

[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 remainunchanged if EOF is encountered immediately. Hence wedo 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 );
}

可以看出,不管加锁的方式如何,它实际上还是会调用系统提供的原始api来进行排他访问,从而避免多线程访问共享资源可能导致读或者写出错的问题。


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

_crt_va_start和_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_IX86void *      _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;elsestr = _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;elsereturn 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



http://www.ppmy.cn/news/171787.html

相关文章

基本输入输出,基本但不简单----小话c语言(4)

开头将文章中代码所在的环境介绍下&#xff1a; [Mac-10.7.1 Lion Intel-based] Q&#xff1a; 看到stdio.h中有这么多关于输入或者输出的函数&#xff0c;怎么会这么多&#xff1f; A&#xff1a; 其实基本的函数不多&#xff0c;不过从易于上层使用的角度&#xff0c;又被封…

小话游戏脚本(一)

小话游戏脚本(一) ( 题记&#xff1a;近来在网上学习到一个新的观点(应该是来自刘未鹏的BLOG :) )&#xff1a;书写是为了更好的学习&#xff0c;这与之前脑子里传道授业解惑的观点颇为迥异&#xff0c;品一品又颇以为然&#xff0c;事物不都是两面的吗&#xff0c;这只是看待角…

小话HTTP Authentication

什么是Authentication&#xff1f; 首先解释两个长的很像、容易混淆的单词&#xff0c;Authentication&#xff08;鉴定、认证&#xff09;和Authorization&#xff08;授权&#xff09;。 Authentication就是要证明你是谁。举个例子&#xff0c;你告诉别人你的名字叫Alice&a…

当写C语言写多了,自然就喜欢C++了----小话c++(1)

[Mac 10.7.1 Lion x64 Intel-based gcc4.2.1 xcode4.2] Q&#xff1a; 解释下标题吧。 A&#xff1a; 依稀记得&#xff0c;写一个数值绝对值的函数时&#xff0c;写到第三个&#xff0c;实在感觉很痛苦&#xff0c;重复了这么多遍&#xff0c;立刻体会了重载和STL的重要…

c库(下)----小话c语言(18)

[Mac 10.7.1 Lion Intel-based x64 gcc4.2.1 xcode4.2 ] Q&#xff1a; 如何解决abs函数传入一个整形数最小值返回溢出的数&#xff1f; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <limits.h>#define PRINT_D(int…

c语言,有时莫名,有时奇妙----小话c语言(25)

作者&#xff1a;陈曦 日期&#xff1a;2012-8-17 12:53:12 环境&#xff1a;[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2] 转载请注明出处 Q1&#xff1a; 为什么下面的输出不按照代码的顺序显示&#xff1f; #include <stdio.h>#include <uni…

开发实用命令和工具----小话c语言(16)

[Mac 10.7.1 Lion Intel-based x64 gcc4.2.1] Q&#xff1a; 有的时候&#xff0c;记得在某个目录下写过某个变量或者其它什么文本形式的东西&#xff0c;但是后来忘记写在哪个文件里了&#xff0c;怎么找到&#xff1f; A&#xff1a; 这个就需要用到grep命令了。它很强大…

小话层次分析法(AHP)

在目标决策领域&#xff0c;有的决策数据信息是量化的&#xff0c;如一个项目的未来收益、消耗成本等&#xff0c;通过对各种信息进行计算可以做出较好的决策&#xff1b;但有的决策数据信息并不全是数字化的&#xff0c;如项目信息为“这个收益更好”、“这个成本更高”这样的…