所以我正在通过K& R第二版做练习。做了一些练习后感觉非常自信我以为我会检查这些功能的实际实现。然后我的信心逃离现场。我无法理解任何一个。
例如,我查看getchar()
:
以下是libio/stdio.h
extern int getchar (void);
所以我按照它来完成并得到这个:
__STDIO_INLINE int
getchar (void)
{
return _IO_getc (stdin);
}
我再次关注libio/getc.c
:
int
_IO_getc (fp)
FILE *fp;
{
int result;
CHECK_FILE (fp, EOF);
_IO_acquire_lock (fp);
result = _IO_getc_unlocked (fp);
_IO_release_lock (fp);
return result;
}
我被带到另一个头文件libio/libio.h
,这是非常神秘的:
#define _IO_getc_unlocked(_fp) \
(_IO_BE ((_fp)->_IO_read_ptr >= (_fp)->_IO_read_end, 0) \
? __uflow (_fp) : *(unsigned char *) (_fp)->_IO_read_ptr++)
这是我最终结束旅程的地方。
我的问题很广泛。这是什么意思呢?通过查看代码,我无法为我的生活找出任何合乎逻辑的东西。看起来像一堆代码一层一层地抽象出来。
更重要的是,什么时候真正从stdin
答案 0 :(得分:24)
_IO_getc_unlocked
是一个无法解决的宏。这个想法是你可以从流中获取一个字符,而不必调用一个函数,希望它足够快,可以用于紧密循环等。
让我们一次拆分一层。首先,什么是_IO_BE
?
/usr/include/libio.h:# define _IO_BE(expr, res) __builtin_expect ((expr), res)
_IO_BE是编译器的提示,expr
通常会评估为res
。当期望为真时,它用于构造代码流更快,但没有其他语义效果。所以我们可以摆脱它,让我们留下:
#define _IO_getc_unlocked(_fp) \
( ( (_fp)->_IO_read_ptr >= (_fp)->_IO_read_end ) \
? __uflow(_fp) : *(unsigned char *)(_fp)->_IO_read_ptr++) )
为了清晰起见,我们将其转换为内联函数:
inline int _IO_getc_unlocked(FILE *fp) {
if (_fp->_IO_read_ptr >= _fp->_IO_read_end)
return __uflow(_fp);
else
return *(unsigned char *)(_fp->_IO_read_ptr++);
}
简而言之,我们有一个指向缓冲区的指针,以及一个指向缓冲区末尾的指针。我们检查指针是否在缓冲区之外;如果没有,我们递增它并返回旧值的任何字符。否则,我们调用__uflow
来重新填充缓冲区并返回新读取的字符。
因此,这允许我们在实际需要执行IO来重新填充输入缓冲区之前避免函数调用的开销。
请记住,标准库函数可能很复杂;他们还可以使用非标准的C语言扩展(例如__builtin_expect
),并且可能不适用于所有编译器。他们这样做是因为他们需要快速,因为他们可以对他们正在使用的编译器做出假设。一般来说,除非绝对必要,否则您自己的代码不应使用此类扩展,因为它会使移植到其他平台更加困难。
答案 1 :(得分:4)
从伪代码到真实代码,我们可以将其分解:
if (there is a character in the buffer)
return (that character)
else
call a function to refill the buffer and return the first character
end
让我们使用the ?: operator:
#define getc(f) (is_there_buffered_stuff(f) ? *pointer++ : refill())
更近一点:
#define getc(f) (is_there_buffered_stuff(f) ? *f->pointer++ : refill(f))
现在我们快到了。要确定是否已经缓冲了某些东西,它会使用文件结构指针和缓冲区中的读指针
_fp->_IO_read_ptr >= _fp->_IO_read_end ?
这实际上测试了我的伪代码的相反条件,“缓冲区为空”,如果是,则调用__uflow(_fp) // "underflow"
,否则,它只是用指针直接进入缓冲区,获取字符,然后递增指针:
? __uflow (_fp) : *(unsigned char *) (_fp)->_IO_read_ptr++)
答案 2 :(得分:2)
我强烈推荐P.J. Plauger The Standard C Library。他提供了标准的背景知识,并提供了每个功能的实现。实现比在glibc或现代C编译器中看到的更简单,但仍然使用像您发布的_IO_getc_unlocked()
这样的宏。
宏将从缓冲数据(可能是ungetc缓冲区)中提取一个字符,或者从流中读取它(可以读取并缓冲多个字节)。
答案 3 :(得分:1)
存在标准库的原因是您不需要知道这些功能的确切植入细节。在某些时候实现库调用的代码必须使用非标准系统调用,这些调用必须处理您可能不关心的问题。如果您正在学习C,请确保除了stdlib之外您还可以了解stdlib以外的其他C程序,但是在了解所涉及的系统调用之前,它仍然没有多大意义。
答案 4 :(得分:0)
getchar()的定义将请求重新定义为stdin中字符的特定请求。
_IO_getc()的定义进行完整性检查以确保FILE *存在且不是End-Of-File,然后它锁定流以防止其他线程破坏对_IO_getc_unlocked()的调用。 / p>
_IO_getc_unlocked()的宏定义只是检查读指针是在文件点的末尾还是在文件点的末尾,如果是,则调用__uflow,如果不是,则返回读指针的char。 / p>
这是所有stdlib实现的标准内容。你不应该看它。事实上,许多stdlib实现将使用汇编语言进行最佳处理,这更加神秘。