Почему конкретные типы данных C объявляются в более чем одном стандартном файле заголовка?
Например, C11 указывает, что size_t
должен быть объявлен в следующих заголовочных файлах:
- stddef.h
- stdio.h
- stdlib.h
- string.h
- time.h
- uchar.h
- wchar.h
При чтении C11 я обнаружил, что существует много других типов данных, объявленных в нескольких стандартных файлах заголовков.
Вопросы
- Скажем, в случае
size_t
. Почему не просто в stddef.h
для простоты?
- Скажем, компилятор C реализует
size_t
в этих файлах заголовков. Гарантируются ли они одинаковое определение в этих файлах заголовков?
Ответы
Ответ 1
Скажем, в случае size_t. Почему не просто в stddef.h для простоты?
Тип используется при объявлении функций во всех этих файлах. Если он не был объявлен в <stdio.h>
, вы получили бы ошибку компиляции, если только вы не включите <stddef.h>
.
Скажем, компилятор C реализует size_t в этих файлах заголовков. Гарантируются ли они одинаковое определение в этих файлах заголовков?
Да, они будут иметь одинаковое определение. Как правило, значение определяется в одном месте в отдельном файле include, который включен другими.
В некоторых случаях может быть возможно изменить определение с помощью параметров компилятора или определить, например, компилятор, который допускает компиляцию 32/64 бит, может определить size_t
как 32 или 64-разрядный неподписанный объект в зависимости от цели, определенной на командной строки компилятора.
Ответ 2
В качестве примера функции, объявленной в stdio.h
, которая требует, чтобы size_t был предварительно определен, рассмотрим snprintf()
. Как бы то ни было, если вы хотите использовать его в своем коде, все, что вам нужно сделать, это #include <stdio.h>
. Если size_t был объявлен только в stddef.h
, вы должны были бы
#include <stddef.h>
#include <stdio.h>
Не только это, но поскольку stdio.h
объявляет snprintf
, используете ли вы его или нет, вам нужно будет включать оба файла каждый раз, когда вам нужно что-либо в stdio.h
, чтобы избежать ошибок компилятора; stdio.h
будет иметь искусственную зависимость от stddef.h
. Это приводит к тому, что ваш исходный код становится более длинным и более хрупким (обратите внимание, что если вы измените порядок двух директив, он также сломается). Вместо этого мы пишем файлы заголовков, чтобы они стояли одни и не зависели от других заголовков, и это то, что комитет стандартизации C принял решение для стандартной библиотеки.
Ответ 3
Там тонкая разница между и внутри - реализация полностью бесплатна для определения size_t
в одном заголовке, если она определена при включении указанных заголовков. Итак, у вас есть два варианта:
- Определите
size_t
в каждом отдельном файле и оберните каждый из них в include guard
- Определите его в одном файле и оберните его в include guard
И да, size_t должен быть определен как указанный, который (glibc):
typedef unsigned long size_t;
или
typedef unsigned int size_t
Они не говорят, что вы должны быть здравомыслящими, они просто говорят, что это нужно определить в то время, когда кто-то включает один из этих заголовков, потому что они зависят от его определения и могут использоваться независимо. Проще говоря, если вы определяете что-то, зависящее от size_t
, тогда size_t
необходимо сначала (ранее) определить.
Как (или, вернее, где) вы это делаете, зависит от вашей реализации.
Ответ 4
Прежде всего, когда вы выполняете #include <stdio.h>
, нет требования, чтобы на самом деле существовал файл в любом месте, называемый stdio.h, или что компилятор ничего делать с таким файлом. Скорее, требование состоит в том, что такая строка должна приводить к определению всех идентификаторов, которые определены как связанные с <stdio.h>
в соответствии со спецификацией. Было бы совершенно законно, если бы компилятор увидел, что #include <stdio.h>
просто включил использование определенных идентификаторов, которые были жестко связаны с компилятором. Поскольку самый простой способ для разработчиков компилятора вести себя так, как требуется спецификацией, состоит в том, чтобы директивы #include <stdio.h>
запускали текст некоторого файла stdio.h
через препроцессор, что многие компиляторы делают, но это не требуется.
Когда специфицирует список "файлы", где size_t
должен быть объявлен, то, что он на самом деле говорит, что директива #include
, которая называет любой из этих файлов, должна создать этот идентификатор в глобальной области. Это можно сделать, если файлы со всеми перечисленными именами содержат определение size_t
или путем включения size_t
в компилятор, но только включение встроенного определения компилятора видит директиву #include
с одним указанных имен.