Поскольку комитет Standard C не стандартизовал простую замену gets(), что это должно быть?
Функция gets
была сначала устарела на C99 и, наконец, удалена в C11. Однако в C-библиотеке нет прямой замены.
fgets()
не является замещающей заменой, поскольку он не разделяет окончательный '\n'
, который может отсутствовать в конце файла. Многие программисты тоже ошибаются.
Существует один слой для удаления строки: buf[strcspn(buf, "\n")] = '\0';
, но это нетривиально и часто вызывает объяснение. Это может быть и неэффективным.
Это контрпродуктивно. Многие новички по-прежнему используют gets()
, потому что их учителя хромают или их устаревшие устаревшие.
Microsoft предложила gets_s()
и многие связанные с ней функции, но она не обрезает лишние строки молчанием, поведение в этом нарушении ограничений не совсем просто.
Как BSD, так и GNU libc имеют getline
, стандартизованный в POSIX, который выделяет или перераспределяет буфер через realloc
...
Каков наилучший способ научить новичков об этом беспорядке?
Ответы
Ответ 1
Характер вопроса таков, что будут спекуляции и мнения. Но мы могли найти некоторую информацию из соображений C99 и стандарта C11.
Обоснование C99, когда gets()
было устаревшим, говорится о следующей причине для его обесценивания:
Поскольку получение не проверяет переполнение буфера, оно обычно небезопасно для использования, когда его вход не находится под контролем программистов. Это вызвало у некоторых вопрос о том, должно ли оно появляться в все. Комитет решил, что получение было полезным и удобным в те особые обстоятельства, когда у программиста есть адекватные контроля над входными данными, и как многолетняя существующая практика, требуется стандартная спецификация. В целом, однако, предпочтительный функция - fgets (см. §7.19.7.2).
Я не думаю, что gets_s()
можно рассматривать как альтернативу. Потому что gets_s()
является дополнительным интерфейсом. C11 фактически рекомендует fgets()
над gets_s()
:
§K.3.5.4.1, проект C11
Функция fgets позволяет правильно написанным программам безопасно обрабатывать слишком строгие строки ввода для хранения в массиве результатов. В целом это требует, чтобы вызывающие лица из fgets обращали внимание на присутствие или отсутствие символа новой строки в массиве результатов. Рассмотрите возможность использования fgets (наряду с любой необходимой обработкой на основе символов новой строки) вместо gets_s.
Таким образом, мы оставим нас с fgets()
как единственную реальную замену для gets()
в ISO C. fgets()
эквивалентно gets()
, за исключением того, что он будет читать в новой строке, если есть буферное пространство. Так стоит ли вводить новый интерфейс, который имеет незначительное улучшение по сравнению с давним и широко используемым (fgets()
)? ИМО, №.
Кроме того, многие приложения реального мира не ограничиваются только ISO C. Таким образом, есть возможность использовать расширения и POSIX getline()
и т.д. В качестве замены.
Если вам нужно найти решение для записи в ISO C, то в любом случае довольно легко написать оболочку вокруг fgets()
, например my_fgets()
, которая удалит новую строку, если она есть.
Конечно, преподавание fgets()
для новичков предполагает объяснение потенциальной проблемы новой строки. Но ИМО, это не так сложно понять, и кто-то, кто собирается учиться, должен уметь быстро схватывать его. Это (поиск последнего символа и замена его, если он является символом "X" ), можно даже считать хорошим упражнением для новичка.
Таким образом, в свете вышеизложенных причин, я бы сказал, что нет необходимости подавать новую функцию в ISO C как истинную замену gets()
.
Ответ 2
Этот вопрос во многом требует спекуляции, не содержащей цитаты из протоколов комиссии или чего-то еще, но в качестве общего принципа комитет (РГ14) обычно избегает изобретать новые интерфейсы и предпочитает документировать и делать строгую существующую практику (например, snprintf
, long long
, типы inttypes.h
и т.д.), а иногда и другие определения стандартов/интерфейсов вне C (например, сложная математика из плавающей точки IEEE, атомная модель из С++ и т.д.). gets
не имеет такой замены для принятия, вероятно, потому что fgets
обычно считается превосходным (он не потерян, когда файл заканчивается без новой строки). Если вам действительно нужна прямая замена, что-то вроде этого работает:
char buf[100];
scanf("%99[^\n]%*1[\n]", buf);
Конечно, это klunky для использования, особенно когда размер буфера является переменным.
Ответ 3
IMO, любая замена должна будет передать size
, а также пункт назначения char *
, требующий изменения кода, которые в значительной степени зависят от каждого случая. Совместимость с одним размером не считалась возможной, так как size
часто теряется/не проходит по временному коду до gets()
. Учитывая, что у нас было 12-летнее предупреждение (C99 по C11), подозревайте, что комитет считает, что проблема не исчезнет к 2011 году.
Ха!
Комитет Standard C должен был сделать замену, которая также была передана в размере места назначения. Как и следующее. (это, вероятно, имеет проблему столкновения имен)
char *gets_replacement(char *s, size_t size);
Я попытался заменить fgets()
на замену, которая использует VLA (необязательно в C11)
char *my_gets(char *dest, size_t size) {
// +2 one for \n and 1 to detect overrun
char buf[size + 2];
if (fgets(buf, sizeof buf, stdin) == NULL) {
// improve error handling - see below comment
if (size > 0) {
*buf = '\0';
}
return NULL;
}
size_t len = strlen(buf);
if (len > 0 && buf[len - 1] == '\n') {
buf[--len] = '\0';
}
// If input would have overrun the original gets()
if (len >= size) {
// or call error handler
if (size > 0) {
*buf = '\0';
}
return NULL;
}
return memcpy(dest, buf, len + 1);
}