理解C内置库函数实现

时间:2011-07-11 19:46:12

标签: c

所以我正在通过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

获得角色

5 个答案:

答案 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实现将使用汇编语言进行最佳处理,这更加神秘。