Стандартные гарантийные буферы C не затрагиваются после их нулевого терминатора?
В различных случаях, когда буфер предоставляется для многих строковых функций стандартной библиотеки, гарантируется ли, что буфер не будет изменен после нулевого терминатора? Например:
char buffer[17] = "abcdefghijklmnop";
sscanf("123", "%16s", buffer);
Является buffer
теперь требуется равным "123\0efghijklmnop"
?
Другой пример:
char buffer[10];
fgets(buffer, 10, fp);
Если строка чтения имеет длину всего 3 символа, можно ли быть уверенным, что шестой символ такой же, как и до вызова fgets?
7 ответов
Каждый отдельный байт в буфере является объектом. Если какая-то часть описания функции sscanf
или же fgets
упоминает об изменении этих байтов или даже подразумевает, что их значения могут измениться, например, заявив, что их значения становятся неуказанными, тогда применяется общее правило: (выделение мое)
6.2.4 Длительность хранения объектов
2 [...] Объект существует, имеет постоянный адрес и сохраняет свое последнее сохраненное значение в течение всего срока его службы. [...]
Это тот же принцип, который гарантирует, что
#include <stdio.h>
int a = 1;
int main() {
printf ("%d\n", a);
printf ("%d\n", a);
}
пытается напечатать 1 дважды. Даже если a
является глобальным, printf
может получить доступ к глобальным переменным, а также описание printf
не упоминает не модифицировать a
,
Ни описание fgets
ни что из sscanf
упоминает об изменении буферов за байтами, которые фактически должны были быть записаны (за исключением случая ошибки чтения), поэтому эти байты не модифицируются.
В проекте стандарта C99 прямо не указано, что должно происходить в этих случаях, но, рассмотрев несколько вариантов, вы можете показать, что он должен работать определенным образом, чтобы соответствовать спецификации во всех случаях.
Стандарт гласит:
% s - соответствует последовательности символов без пробелов.252)
Если модификатор длины l отсутствует, соответствующий аргумент должен быть указателем на начальный элемент массива символов, достаточно большой, чтобы принять последовательность и завершающий нулевой символ, который будет добавлен автоматически.
Вот пара примеров, которые показывают, что это должно работать так, как вы предлагаете соответствовать стандарту.
Пример А:
char buffer[4] = "abcd";
char buffer2[10]; // Note the this could be placed at what would be buffer+4
sscanf("123 4", "%s %s", buffer, buffer2);
// Result is buffer = "123\0"
// buffer2 = "4\0"
Пример Б:
char buffer[17] = "abcdefghijklmnop";
char* buffer2 = &buffer[4];
sscanf("123 4", "%s %s", buffer, buffer2);
// Result is buffer = "123\04\0"
Обратите внимание, что интерфейс sscanf не предоставляет достаточно информации, чтобы действительно знать, что они отличались. Таким образом, если пример B должен работать должным образом, он не должен связываться с байтами после нулевого символа в примере A. Это потому, что он должен работать в обоих случаях в соответствии с этим битом спецификации.
Так что неявно это должно работать, как вы заявили из-за спецификации.
Подобные аргументы могут быть размещены для других функций, но я думаю, что вы можете увидеть идею из этого примера.
ПРИМЕЧАНИЕ. Предоставление ограничений размера в формате, например "%16s", может изменить поведение. По спецификации было бы функционально приемлемо для sscanf обнулить буфер до его пределов перед записью данных в буфер. На практике большинство реализаций выбирают производительность, что означает, что они оставляют оставшуюся часть в покое.
Когда целью спецификации является выполнение такого обнуления, это обычно указывается явно. strncpy является примером. Если длина строки меньше, чем указанная максимальная длина буфера, он заполнит оставшееся пространство пустыми символами. Тот факт, что та же самая "строковая" функция может возвращать и неоконченную строку, делает эту функцию одной из самых распространенных для людей, которые используют свою собственную версию.
Что касается Fgets, аналогичная ситуация может возникнуть. Единственный недостаток заключается в том, что в спецификации явно указано, что если ничего не читается, буфер остается нетронутым. Приемлемая функциональная реализация может обойти это, проверив, есть ли хотя бы один байт для чтения перед обнулением буфера.
Стандарт несколько двусмысленен в этом, но я думаю, что разумное толкование этого заключается в том, что ответ таков: да, в буфер не разрешается записывать больше байтов, чем в нем читается + ноль. С другой стороны, более строгое чтение / толкование текста может сделать вывод, что ответ - нет, гарантии нет. Вот что говорит об этом публично доступный проект fgets
,
char *fgets(char * restrict s, int n, FILE * restrict stream)
;
fgets
функция читает не более, чем на количество символов, указанноеn
из потока, на который указываетstream
в массив, на который указываетs
, Никакие дополнительные символы не читаются после символа новой строки (который сохраняется) или после конца файла. Нулевой символ записывается сразу после последнего прочитанного символа в массив.
fgets
функция возвращаетs
если успешно Если обнаружен конец файла и в массив не были прочитаны символы, содержимое массива остается неизменным и возвращается нулевой указатель. Если во время операции возникает ошибка чтения, содержимое массива неопределенно и возвращается нулевой указатель.
Существует гарантия того, сколько он должен прочитать с ввода, т.е. прекратить чтение на новой строке или EOF и не читать больше, чем n-1
байт. Хотя ничего явно не сказано о том, сколько разрешено записывать в буфер, общеизвестно, что fgets
"s n
Параметр используется для предотвращения переполнения буфера. Немного странно, что в стандарте используется неоднозначный термин " чтение", что не обязательно означает, что gets
не может записать в буфер больше, чем n
байт, если вы хотите придираться к используемой терминологии. Но обратите внимание, что в обоих случаях используется одна и та же терминология "чтения": n
-лимит и лимит EOF/ новой строки. Так что если вы интерпретируете n
"читать" как ограничение записи в буфер, тогда [для согласованности] вы можете / должны интерпретировать другое "чтение" таким же образом, то есть не записывать больше, чем то, что читается, когда строка короче буфера.
С другой стороны, если вы различаете использование словосочетания "read into" (="write") и просто "read", то вы не можете прочитать текст комитета таким же образом. Вы гарантированно не будете "читать в" (= "писать в") массив больше, чем n
байт, но если строка ввода завершается раньше символом новой строки или EOF, вы гарантированно только гарантируете, что остальная часть (входных данных) не будет "прочитана", но подразумевает ли это, что в не будет "прочитано" (=) записано в ") буфер неясен при этом более строгом чтении. Важнейшим вопросом является ключевое слово "into", которое исключено, поэтому проблема заключается в том, является ли завершение, данное мной в скобках в следующей модифицированной цитате, предполагаемой интерпретацией:
Никакие дополнительные символы не читаются [в массив] после символа новой строки (который сохраняется) или после конца файла.
Откровенно говоря, одно постусловие, указанное в формуле (и в данном случае оно будет довольно коротким), было бы намного более полезным, чем приведенное мною словоблудие...
Я не могу попытаться проанализировать их рецензию о *scanf
семья, потому что я подозреваю, что это будет еще сложнее, учитывая все другие вещи, которые происходят в этих функциях; их рецензия на fscanf
составляет около пяти страниц... Но я подозреваю, что аналогичная логика применима.
гарантируется ли, что буфер не будет изменен после нулевого терминатора?
Нет, нет гарантии.
Требуется ли для буфера значение "123\0efghijklmnop"?
Да. Но это только потому, что вы использовали правильные параметры для функций, связанных со строками. Если вы испортите длину буфера, введите модификаторы sscanf
и такое, то у вас программа скомпилируется. Но, скорее всего, он потерпит неудачу во время выполнения.
Если строка чтения имеет длину всего 3 символа, можно ли быть уверенным, что шестой символ такой же, как и до вызова fgets?
Да. однажды fgets()
Если у вас есть 3-х символьная строка ввода, она хранит ввод в предоставленном буфере и не заботится о сбросе предоставленного пространства вообще.
Требуется ли для буфера значение "123\0efghijklmnop"?
Вот buffer
это просто состоит из 123
строка гарантированно оканчивается на NUL.
Да память выделенная для массива buffer
не будет выделяться, однако вы проверяете / ограничиваете свою строку buffer
может иметь только 16
элементы char, которые вы можете прочитать в любой момент времени. Теперь зависит, пишете ли вы только один символ или максимум, что buffer
могу взять.
Например:
char buffer[4096] = "abc";`
на самом деле делает что-то ниже,
memcpy(buffer, "abc", sizeof("abc"));
memset(&buffer[sizeof("abc")], 0, sizeof(buffer)-sizeof("abc"));
Стандарт настаивает на том, что если инициализируется какая-либо часть массива char, то есть все, из чего он состоит, в любой момент, пока не будет соблюдена граница памяти.
Зависит от используемой функции (и в меньшей степени от ее реализации). sscanf
начнёт писать, когда встретит свой первый непробельный символ, и продолжит писать до первого пробельного символа, где добавит завершающий символ 0
и вернуться. Но такая функция, как strncpy
(классно) обнуляет остаток буфера.
Однако в стандарте C нет ничего, что предписывало бы поведение этих функций.
Нет никаких гарантий от стандарта, поэтому функции sscanf
а также fgets
рекомендуется использовать (по отношению к размеру буфера), как вы показываете в своем вопросе (и использование fgets
считается предпочтительным по сравнению с gets
).
Однако некоторые стандартные функции используют нулевой терминатор в своей работе, например strlen
(но я полагаю, вы спрашиваете о модификации строки)
РЕДАКТИРОВАТЬ:
В вашем примере
fgets(buffer, 10, fp);
неприкосновенность символов после 10-го гарантируется (содержание и длина buffer
не будет рассматриваться fgets
)
EDIT2:
Более того, при использовании fgets
имейте в виду, что '\n'
будет храниться в буферах. например
"123\n\0fghijklmnop"
вместо ожидаемого
"123\0efghijklmnop"