Подтвердить что ты не робот

Понимание встроенных реализаций библиотечных функций 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

4b9b3361

Ответ 1

_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), которые НЕ являются стандартными и могут НЕ работать со всеми компиляторами. Они делают это, потому что им нужно быть быстрыми, и потому что они могут делать предположения о том, какой компилятор они используют. Вообще говоря, ваш собственный код не должен использовать такие расширения, если это абсолютно необходимо, поскольку это затруднит перенос на другие платформы.

Ответ 2

Переходя от псевдокода к реальному коду, мы можем его сломать:

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

Позвольте использовать оператор?::

#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++)

Ответ 3

Я очень рекомендую Стандартную библиотеку C П. Дж. Плагера. Он предоставляет справочную информацию по стандарту и обеспечивает реализацию каждой функции. Реализация проще, чем вы увидите в glibc или в современном компиляторе C, но все еще используете макросы, такие как _IO_getc_unlocked(), которые вы разместили.

Макрос собирается вытащить символ из буферизованных данных (который может быть буфером ungetc) или прочитать его из потока (который может читать и буферизовать несколько байтов).

Ответ 4

Причина, по которой существует стандартная библиотека, заключается в том, что вам не нужно знать точную информацию о имплантации этих функций. Код, который реализует вызовы библиотеки в какой-то момент, должен использовать нестандартные системные вызовы, которые должны решать проблемы, которые могут вас не беспокоить. Если вы изучаете C, убедитесь, что вы можете понять другие программы на C, кроме stdlib, как только вы получите немного более быстрый взгляд на stdlib, но он все равно не будет иметь большого смысла, пока вы не поймете задействованные системные вызовы.

Ответ 5

Определение getchar() переопределяет запрос как конкретный запрос для символа из stdin.

Определение _IO_getc() делает проверку работоспособности, чтобы убедиться, что FILE * существует и не является конечным файлом, затем блокирует поток, чтобы другие потоки не нарушали вызов _IO_getc_unlocked().

Определение макроса _IO_getc_unlocked() просто проверяет, находится ли указатель чтения в конце файла или после него, и либо вызывает __uflow, если он есть, либо возвращает char в указатель чтения, если он не является.

Это стандартный материал для всех реализаций stdlib. Вы не должны когда-либо смотреть на это. Фактически, многие реализации stdlib будут использовать язык ассемблера для оптимальной обработки, что еще более загадочно.