Подтвердить что ты не робот

Поддерживаются ли стандартные буферные буферы C за нулевым терминатором?

В различных случаях, когда буфер предоставляет стандартной библиотеке множество строковых функций, гарантировано ли, что буфер не будет изменен за пределами нулевого терминатора? Например:

char buffer[17] = "abcdefghijklmnop";
sscanf("123", "%16s", buffer);

Теперь buffer требуется равное "123\0efghijklmnop"?

Другой пример:

char buffer[10];
fgets(buffer, 10, fp);

Если строка чтения имеет длину всего 3 символа, можно ли быть уверенным, что 6-й символ такой же, как до вызова fgets?

4b9b3361

Ответ 1

Каждый отдельный байт в буфере является объектом. Если какая-либо часть описания функции 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 не упоминают модификацию буферов за байтами, которые на самом деле должны были быть написаны (за исключением случая ошибки чтения), поэтому эти байты не изменяются.

Ответ 2

В стандарте C99 draft явно не указано, что должно произойти в этих случаях, но, рассматривая несколько вариантов, вы можете показать, что он должен работать определенным образом, он соответствует спецификации во всех случаях.

В стандарте говорится:

% s - Соответствует последовательности символов небелого пробела .252)

Если нет модификатора длины l, соответствующий аргумент должен быть указатель на исходный элемент массива символов, достаточно большой, чтобы принять последовательность и завершающий нулевой символ, который будет добавлен автоматически.

Здесь пара примеров, которые показывают это, должна работать так, как вы предлагаете, чтобы соответствовать стандарту.

Пример A:

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"

Пример B:

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, может возникнуть аналогичная ситуация. Единственная проблема заключается в том, что спецификация явно заявляет, что если ничего не читается, буфер остается нетронутым. Допустимая функциональная реализация может обойти это, проверяя, есть ли хотя бы один байт для чтения перед обнулением буфера.

Ответ 3

Стандарт несколько неоднозначен по этому поводу, но я думаю, что разумное его чтение заключается в том, что ответ таков: да, ему не разрешено записывать больше байтов в буфер, чем он читает + null. С другой стороны, более строгое чтение/интерпретация текста может заключаться в том, что ответ отрицательный, нет никакой гарантии. Здесь публично анонсированный проект говорит о fgets.

char *fgets(char * restrict s, int n, FILE * restrict stream);

Функция fgets считывает не более одного числа, указанного числом n из потока, на который указывает stream, в массив, на который указывает s. Никакие дополнительные символы не читаются после символа новой строки (который сохраняется) или после окончания файла. Нулевой символ записывается сразу после последнего символа, который считывается в массив.

Функция fgets возвращает s в случае успеха. Если конец файла встречается и в массив не считываются символы, содержимое массива остается неизменным и возвращается нулевой указатель. Если во время операции возникает ошибка чтения, содержимое массива неопределенно и возвращается нулевой указатель.

Здесь есть гарантия того, насколько он должен считываться с ввода, т.е. прекратить чтение в новой строке или EOF и не читать больше, чем n-1 байтов. Хотя ничто не сказано явно о том, сколько разрешено писать в буфер, общеизвестно, что параметр fgets n используется для предотвращения переполнения буфера. Немного странно, что стандарт использует двусмысленный термин, который может не обязательно подразумевать, что gets не может записать в буфер больше, чем n байты, если вы хотите использовать терминологию, которую он использует. Но обратите внимание, что для обеих проблем используется одна и та же "прочитанная" терминология: n -limit и предел EOF/новой строки. Поэтому, если вы интерпретируете n -связанный "прочитанный" как лимит записи буфера, тогда [для согласованности] вы можете/должны интерпретировать другое "чтение" таким же образом, то есть не писать больше, чем то, что оно читает, когда строка короче буфера.

С другой стороны, если вы различаете использование фразы-глагола "читать в" (= "писать" ) и просто "читаете", то вы не можете читать текст комитета одинаково. Вам гарантировано, что он не будет "читать" (= "писать" ) в массив больше, чем n байт, но если входная строка будет прервана раньше новой строкой или EOF, вам гарантируется только остальное (из ввод) не будет "прочитан", но независимо от того, будет ли это подразумеваться, не будет "прочитано" (= "написано на" ), буфер неясен при этом более строгом чтении. Ключевой проблемой является ключевое слово "в", которое устранено, поэтому проблема заключается в том, является ли задание, указанное мною в скобках в следующей модифицированной цитате, предполагаемой интерпретацией:

Никакие дополнительные символы не читаются [в массив] после символа новой строки (который сохраняется) или после окончания файла.

Честно говоря, один postcondition, заявленный как формула (и был бы довольно коротким в этом случае), был бы намного более полезным, чем приведенный мною слово...

Я не могу потрудиться, чтобы попытаться проанализировать их запись о семействе *scanf, потому что я подозреваю, что это будет еще сложнее, учитывая все другие вещи, которые происходят в этих функциях; их запись для fscanf составляет около пяти страниц... Но я подозреваю, что применяется аналогичная логика.

Ответ 4

гарантируется, что буфер не будет изменен за пределами нулевого терминатор?

Нет, нет никакой гарантии.

Теперь требуется буфер для равного "123\0efghijklmnop"?

Да. Но это только потому, что вы использовали правильные параметры для связанных с строкой функций. Если вы испортили длину буфера, модификаторы ввода до sscanf и т.д., То программа будет компилироваться. Но это скорее всего не удастся во время выполнения.

Если строка чтения имеет длину всего 3 символа, можно ли быть уверенным, что 6-й символ такой же, как до вызова fgets?

Да. После цифры fgets() у вас есть входная строка из 3 символов, она сохраняет вход в предоставленном буфере, и он не заботится о reset предоставленного пространства вообще.

Ответ 5

Теперь требуется буфер для равного "123\0efghijklmnop"?

Здесь buffer состоит только из строки 123, гарантированной завершением в NUL.

Да память, выделенная для массива buffer, не будет де-распределена, однако вы убедитесь, что/ограничение вашей строки buffer может содержать только 16 char элементы, которые вы можете прочитать в ней момент времени. Теперь зависит, пишите ли вы только один char или максимум, что может сделать buffer.

Например:

char buffer[4096] = "abc";` 

на самом деле что-то делает,

memcpy(buffer, "abc", sizeof("abc"));
memset(&buffer[sizeof("abc")], 0, sizeof(buffer)-sizeof("abc"));

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

Ответ 6

Нет никаких гарантий от стандарта, поэтому рекомендуется использовать функции sscanf и fgets (по размеру буфера), как вы видите в своем вопросе (и использование fgets считается предпочтительным по сравнению с gets).

Однако некоторые стандартные функции используют нулевой ограничитель в своей работе, например. strlen (но я полагаю, вы спрашиваете о модификации строки)

EDIT:

В вашем примере

fgets(buffer, 10, fp);

неприкосновенные символы после 10-го гарантированы (содержание и длина buffer не будут считаться fgets)

EDIT2:

Кроме того, при использовании fgets помните, что '\n' будет храниться в буферах. например.

 "123\n\0fghijklmnop"

вместо ожидаемого

 "123\0efghijklmnop"

Ответ 7

Зависит от используемой функции (и в меньшей степени ее реализации). sscanf начнет писать, когда он встретит свой первый символ без пробелов и продолжит писать до первого символа пробела, где он добавит финишную 0 и вернется. Но функция типа strncpy (классно) обнуляет остальную часть буфера.

Однако в стандарте C ничего не говорится о том, как эти функции действуют.