Почему аргументы fopen ограничены квалификацией в файле заголовка C и <stdio.h>?
Стандартная библиотечная функция fopen
объявляется в <stdio.h>
как:
FILE *fopen(const char * restrict filename, const char * restrict mode);
Это также то, как прототип функции появляется в стандарте C.
Почему аргументы restrict
квалифицированы?
Ответы
Ответ 1
Кажется, нет никаких веских причин для аргументов fopen
, которые должны быть restrict
квалифицированы в прототипе в <stdio.h>
.
restrict
Определение указателя - это обещание программиста, что объект, на который указывает указанный указатель, будет доступен только через этот и любой другой указатель, основанный на нем в пределах данной области.
В прототипе функции такое обещание является спорным.
Nate Eldredge предложил объяснение, что это означает, что вам запрещено указывать имя файла и режим в одну строку. Но это утверждение кажется несущественным, и такое ограничение не требуется и не упоминается в определении fopen
в разделе 7.21.5.3 Стандарта C.
Прототип для setbuf
имеет тот же restrict
квалификатор для своих аргументов:
void setbuf(FILE * restrict stream, char * restrict buf);
Я понимаю, почему разработчик будет квалифицировать stream
и buf
с ключевым словом ограничения, чтобы сообщить компилятору, что изменение структуры FILE
во время действия setbuf
не влияет на содержимое buf
и наоборот.
То же самое можно сказать и о реализации fopen
, где программист сообщает компилятору, что структура FILE
, управляемая fopen
, не перекрывает имя файла или режим. Но определение как filename
, так и mode
является ложным обещанием, поскольку оно подразумевает ограничение, которое отсутствует в стандарте.
Мое заключение состоит в том, что restrict
квалификационные аргументы в прототипах объявления функций не нужны и обманчивы. Это уменьшает читаемость и вызывает ложные интерпретации.
Ответ 2
Это означает, что данные не будут изменены. Фактического исполнения этого обещания не существует, как это часто бывает в C, но неспособность жить этим обещанием может привести к поведению undefined.
По сути, квалификатор restrict
означает, что никакой другой указатель не изменит значение имени файла или режима доступа, пока дескриптор файла действителен. Вот выдержка из веб-сайта, которая охватывает это довольно неплохо:
Ограничение - это контракт "без каких-либо факторов, связанных с данными", между программистом и компилятором. Компилятор полагается на эту информацию для оптимизации. Если данные, по сути, сглажены, результаты undefined, и программист не должен ожидать, что компилятор выдаст предупреждение. Компилятор предполагает, что программист не лежит.
Итак, почему он там? Поскольку, когда была написана библиотечная функция fopen
, автор решил попросить вас не изменять строки, которые вы передаете, после того как вы попросите его открыть файл. Чтобы быть откровенным, я не понимаю, почему это было бы даже запрошено, потому что, насколько мне известно, после открытия файла имеет смысл только файловый дескриптор, и имя файла и режим никогда не упоминаются снова внутри.
Ответ 3
Формальное определение значения restrict
приведено в разделе раздела 6.7.3.1 из C2011. Обратите внимание, в частности, что элементы списка параметров прототипа функции, который не является частью определения функции, не соответствуют условиям, изложенным в параграфе 1 этого раздела:
Пусть D - объявление обычного идентификатора, которое предоставляет средство для обозначения объекта P в качестве ограничивающего ограничения указателя на тип T.
Список параметров такого прототипа не предоставляет никаких средств для такого обозначения. Поэтому ничто в этом разделе не дает прямого влияния на квалификатор restrict
в этом контексте.
Лучшая интерпретация, вероятно, является уведомлением пользователей функции о том, что параметры объявлены с помощью определителя restrict
в определении функции, где этот определитель имеет эффект.
Обратите также внимание, однако, что restrict
не является полным обещанием отсутствия псевдонимов. Вместо этого это квалифицированное обещание, что если цель restrict
-qualified pointer будет изменена каким-либо образом, тогда она будет доступна только через этот указатель в пределах области указателя.
Возвращаясь к fopen()
, тогда в t20 > в прототипе функции нечего сказать о определенности поведения любого вызова функции. То есть это не по своей сути undefined:
char s[] = "r";
FILE *f = fopen(s, s);
Выполнение функции - это еще одна история - или это было бы, если бы указательные цели не были также const
-qualified. Предположим, что прототип выражает эффективные квалификаторы для определения функции, если два аргумента друг друга и, если их цель была изменена функцией, тогда функция будет вызывать поведение undefined, если она обратилась к target через другой указатель. Поскольку const
квалификация целевых указателей означает, что функция будет вызывать UB, изменяя цель в первую очередь, restrict
квалификация спорна.С >
Выполнение функции - это еще одна история. Хотя квалификация const
целевых показателей параметра означает, что мы должны предположить, что fopen()
не будет пытаться модифицировать их с помощью этих указателей, сами цели не обязательно const
. Их можно было бы модифицировать вне fopen()
, используя значение const
l, и это все равно будет иметь отношение к квалификации restrict
. Предположим, что прототип выражает эффективные квалификаторы для определения функции. Если два аргумента друг друга и если их общая цель изменена в любом месте программы, тогда fopen()
будет вызывать UB, когда он обращается к цели с помощью обоих указателей.