Почему «fopen» в C принимает «const char *» в качестве второго аргумента? - PullRequest
13 голосов
/ 25 марта 2010

Меня всегда удивляло, что функция C "fopen" принимает "const char *" в качестве второго аргумента. Я думаю, что было бы проще как прочитать ваш код, так и реализовать код библиотеки, если бы в stdio.h были определены битовые маски, такие как «IO_READ» и т. Д., Чтобы вы могли делать такие вещи, как:

FILE* myFile = fopen("file.txt", IO_READ | IO_WRITE);

Есть ли программная причина того, как она есть на самом деле, или она просто историческая? (то есть "Так оно и есть.")

Ответы [ 8 ]

6 голосов
/ 25 марта 2010

Одно слово: наследие. К сожалению, мы должны жить с этим.

Просто предположение: может быть, в то время «const char *» казалось более гибким решением, потому что оно никоим образом не ограничено. Битовая маска может иметь только 32 различных значения. Выглядит как ЯГНИ для меня сейчас.

Больше предположений: парни ленивы, и для написания "rb" требуется меньше печатать, чем для MASK_THIS | MASK_THAT:)

4 голосов
/ 15 июля 2015

Самое раннее упоминание о fopen, которое я нашел, содержится в первом издании Kernighan & Ritchie "The C Programming Language" (K & R1), опубликованном в 1978 году.

Он показывает пример реализации fopen, которая, предположительно, является упрощенной версией кода в реализации стандартной библиотеки C того времени. Вот сокращенная версия кода из книги:

FILE *fopen(name, mode)
register char *name, *mode;
{
    /* ... */
    if (*mode != 'r' && *mode != 'w' && *mode != 'a') {
        fprintf(stderr, "illegal mode %s opening %s\n",
            mode, name);
        exit(1);
    }
    /* ... */
}

Глядя на код, ожидалось, что mode будет строкой из 1 символа (без "rb", без различия между текстом и двоичным кодом). Если вы передали более длинную строку, любые символы после первого были молча проигнорированы. Если вы передали неверный mode, функция напечатает сообщение об ошибке и прекратит работу вашей программы, а не возвратит нулевой указатель (я предполагаю, что фактическая версия библиотеки этого не сделала) В книге подчеркивается простой код, а не проверка ошибок.

Трудно быть уверенным, особенно учитывая, что книга не тратит много времени на объяснение параметра mode, но похоже, что он был определен как строка просто для удобства. Один символ тоже сработал бы, но, по крайней мере, строка делает возможным дальнейшее расширение (то, о чем книга не упоминает).

4 голосов
/ 26 марта 2010

Я считаю, что одним из преимуществ строки символов вместо простой битовой маски является то, что она допускает специфичные для платформы расширения, которые не являются настройками битов. Чисто гипотетически:

FILE *fp = fopen("/dev/something-weird", "r+,bs=4096");

Для этой штуковины вызову open() нужно сообщить размер блока, и разные вызовы могут использовать радикально разные размеры и т. Д. Конечно, теперь ввод / вывод был организован довольно хорошо (изначально это было не так - устройства были чрезвычайно разнообразны, а механизмы доступа далеко не унифицированы), поэтому это редко кажется необходимым. Но строковый аргумент открытого режима позволяет эту расширяемость гораздо лучше.

На мэйнфреймах IBM MVS o / s функция fopen() действительно принимает дополнительные аргументы в общих чертах, описанных здесь - как отмечено Эндрю Хенле (благодарю вас!). Страница руководства содержит пример вызова (слегка переформатированный):

FILE *fp = fopen("myfile2.dat", "rb+, lrecl=80, blksize=240, recfm=fb, type=record"); 

Базовый open() должен быть дополнен вызовом ioctl() (контроль ввода-вывода) или fcntl() (управление файлом) или функциями, скрывающими их для достижения аналогичных эффектов.

4 голосов
/ 26 марта 2010

Я бы предположил, что это одно или несколько из следующего (к сожалению, я не смог быстро найти какие-либо вспомогательные ссылки, так что это, вероятно, останется спекуляцией):

  1. Кернигану или Ритчи (или тому, кто придумал интерфейс для fopen()) просто пришла в голову идея указать режим с использованием строки вместо растрового изображения
  2. Возможно, они хотели, чтобы интерфейс был похож, но заметно отличался от интерфейса системных вызовов Unix open(), поэтому он сразу был бы знаком, но не по ошибке компилирован с константами, определенными для Unix вместо библиотеки C

Например, предположим, что мифический стандарт C fopen(), который принял параметр режима растрового изображения, использовал идентификатор OPENMODE_READONLY, чтобы указать, что файл, который сегодня указан строкой режима "r". Теперь, если кто-то сделал следующий вызов для программы, скомпилированной на платформе Unix (и что был включен заголовок, который определяет O_RDONLY):

fopen( "myfile", O_RDONLY);

Не было бы ошибки компилятора, но если OPENMODE_READONLY и O_RDONLY не были определены как один и тот же бит, вы получите непредвиденное поведение. Конечно, было бы целесообразно, чтобы стандартные имена C были определены так же, как имена Unix, но, возможно, они хотели исключить необходимость такого типа связи.

Опять же, это могло вообще не приходить им в голову ...

3 голосов
/ 26 марта 2010

Деннис Ритчи может сказать, что это http://cm.bell -labs.com / cm / cs / who / dmr / chist.html

В частности, Леск написал «портативный Пакет ввода / вывода '[Lesk 72], который был позже переработан, чтобы стать стандартом C` Процедуры ввода / вывода

Итак, я говорю, спросите Майк Леск , опубликуйте результат здесь как ответ на свой вопрос и заработайте за это стеки очков. Хотя, возможно, вы захотите, чтобы вопрос звучал немного менее критично; -)

2 голосов
/ 10 июля 2018

Деннис Ритчи (в 1993 г.) написал статью об истории C и о том, как она постепенно развивалась из B. Некоторые из проектных решений были мотивированы, избегая изменений исходного кода в существующем коде, написанном на B или эмбриональные версии C.

В частности, Леск написал «портативный Пакет ввода / вывода '[Lesk 72], который был позже переработан, чтобы стать стандартом C` Процедуры ввода / вывода

Препроцессор C не был представлен до 1972/3, поэтому пакет ввода-вывода Lesk был написан без него! (В очень раннем, еще не-C, указатели помещаются в целые числа на платформах, являющихся использовался, и было совершенно нормально назначать возвращаемое значение неявного int указателю.)

Многие другие изменения произошли около 1972-3, но самым важным было введение препроцессора, частично по настоянию Алана Снайдера [Snyder 74]

Без #include и #define выражение типа IO_READ | IO_WRITE было бы недопустимым.

В 1972 году параметры для вызовов fopen могут выглядеть в типичном источнике без CPP:

FILE *fp = fopen("file.txt", 1);       // magic constant integer literals
FILE *fp = fopen("file.txt", 'r');     // character literals
FILE *fp = fopen("file.txt", "r");     // string literals

Волшебные целочисленные литералы, очевидно, ужасны, поэтому, к сожалению, очевидно, что наиболее эффективный вариант (который позже был принят Unix для open(2)) был исключен из-за отсутствия препроцессора.

Символьный литерал явно не расширяемый; по-видимому, это было очевидно для разработчиков API еще тогда. Но этого было бы достаточно (и более эффективно) для ранних реализаций fopen: они поддерживали только односимвольные строки, проверяя, чтобы *mode был r, w или a. (См. @ ответ Кита Томпсона .) Видимо r+ для чтения + записи (без усечения) появилось позже. (См. fopen(3) для современной версии.)

C имел тип данных (добавлен к B 1971 как один из первых этапов создания эмбрионального C, поэтому он все еще был новым в 1972 году. Оригинальный B не имел char, будучи написанным для машин, которые упаковывают несколько символов в слово, char() была функцией, которая индексировала строку! См. статью по истории Ричи.)

Использование однобайтовой строки эффективно передает char по const-ссылке со всеми дополнительными издержками доступа к памяти, поскольку библиотечные функции не могут быть встроенными. (И примитивные компиляторы, вероятно, ничего не вставляли, даже тривиальные функции (в отличие от fopen) в том же модуле компиляции, где он уменьшал бы общий размер кода, чтобы встроить их; крошечные вспомогательные функции современного стиля полагаются на современные компиляторы, чтобы встроить их). *


PS: Ответ Стива Джессопа с той же цитатой вдохновил меня на написание этого.

Возможно, связано: strcpy () возвращаемое значение . strcpy тоже, вероятно, был написан довольно рано.

2 голосов
/ 26 марта 2010

Я должен сказать, что я благодарен за это - я знаю, чтобы напечатать "r" вместо IO_OPEN_FLAG_R или это был IOFLAG_R или SYSFLAGS_OPEN_RMODE или что-то еще

0 голосов
/ 26 марта 2010

Как говорит Туомас Пелконен, это наследие.

Лично мне интересно, неужели некоторые ошибочные соки воспринимаются как лучшие из-за меньшего количества набранных символов? В старину время программистов ценилось более высоко, чем сегодня, так как оно было менее доступным, а компиляторы были не так хороши и все такое.

Это всего лишь предположение, но я понимаю, почему некоторые люди предпочитают сохранять несколько символов тут и там (обратите внимание на отсутствие многословия в именах стандартных библиотечных функций ... Я представляю строку strstr из string.h "strchr", как, вероятно, лучшие примеры ненужной краткости).

...