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