кто бесплатно setvbuf буфер?

Итак, я копался в том, как реализована часть stdio libc, и я столкнулся с другим вопросом. Глядя на man setvbuf я вижу следующее:

Когда над файлом происходит первая операция ввода-вывода, вызывается malloc(3) и создается буфер.

Это имеет смысл, ваша программа не должна иметь malloc для ввода-вывода, если только вы ее не используете. Моя интуиция на это такова, что libc сама наведет здесь свой беспорядок. Я могу только предположить, что это так, потому что valgrind не сообщает об утечках памяти (они, конечно, могут сделать что-то грязное и не выделять ее напрямую через malloc... но мы предположим, что сейчас он буквально использует malloc).

Но вы также можете указать свой собственный буфер...

int main() {
    char *p = malloc(100);
    setvbuf(stdio, p, _IOFBF, 100);
    puts("hello world");
}

О нет, утечка памяти! Валгринд подтверждает это. Таким образом, кажется, что всякий раз, когда stdio выделяет буфер самостоятельно, он автоматически удаляется (самое позднее при выходе из программы, но, возможно, при закрытии потока). Но если вы указываете буфер явно, то вы должны очистить его самостоятельно.

Однако есть одна загвоздка. На странице руководства также говорится следующее:

Вы должны убедиться, что пространство, на которое указывает buf, все еще существует к временному потоку, что также происходит при завершении программы. Например, следующее неверно:

Теперь это становится интересным для стандартных потоков. Как правильно очистить для них выделенный вручную буфер, поскольку они закрываются при завершении программы? Я мог бы представить себе «очистить это, когда я закрою флаг» внутри файловой структуры, но это становится волосатым, потому что, если я правильно прочитаю это, сделаю что-то вроде этого:

setvbuf(stdout, 0, _IOFBF, 0);
printf("hello ");
setvbuf(stdout, 0, _IOLBF, 0);
printf("world\n");

вызовет 2 выделения памяти стандартной библиотекой из-за этого предложения:

Если аргумент buf равен NULL, затрагивается только режим; новый буфер будет выделен при следующей операции чтения или записи.

EDIT: дополнение к моему вопросу. Поскольку ясно, что я должен free передать любые буферы в setvbuf, если я действительно использую его на stdout, есть ли какой-нибудь практический способ free его? Он должен жить до конца программы. Лучшее, что я могу придумать, это fclose(stdout) затем освободить его или использовать статический буфер, как упоминали некоторые люди. Я спрашиваю, потому что это кажется неуклюжим дизайнерским решением.


person Evan Teran    schedule 19.04.2010    source источник


Ответы (3)


Также из справочной страницы (по крайней мере, в моей системе):

Если buf не равен NULL, ответственность за освобождение(3) этого буфера после закрытия потока лежит на вызывающей стороне.

То есть, вы выделили его, вы его освободили.

Перед выходом вы можете сами закрыть потоки, тем самым освободив буфер. В качестве альтернативы вы можете сбросить потоки и снова вызвать setvbuf с аргументом буфера NULL, чтобы вернуться к управляемому библиотекой буферу или небуферизованному вводу-выводу.

person outis    schedule 19.04.2010
comment
Обновлен ответ на ссылку на страницу. Это OS X, но справочная страница взята из FreeBSD (freebsd.org/cgi/ man.cgi?query=setvbuf). - person outis; 20.04.2010
comment
Трудно понять, как все могло бы быть иначе, поскольку можно было бы предоставить статический буфер, который не должен освобождаться с помощью free(). - person ; 20.04.2010
comment
@Neil: действительно, меня больше интересовало, имеет ли stdlib утечку памяти по дизайну, поскольку нет способа получить выделенный буфер из потока. - person Evan Teran; 20.04.2010
comment
Повторный вызов setvbuf с аргументом буфера NULL не разрешен стандартом (см. цитату в моем ответе). - person Jerry Coffin; 20.04.2010

По крайней мере, согласно стандарту C ваш последний сценарий просто не разрешен: «Функция setvbuf может использоваться только после того, как поток, на который указывает stream, был связан с открытым файлом и перед любой другой операцией (кроме неудачного вызова в setvbuf) выполняется в потоке». (C99, §7.19.5.6/2).

Что касается того, когда освобождать память в более простых случаях, то можно вызвать atexit(), чтобы зарегистрировать обратный вызов, который освободит память после выхода из main(), но до того, как управление будет возвращено ОС.

person Jerry Coffin    schedule 19.04.2010
comment
интересный. Так что мой последний пример попадает в категорию неопределенного поведения. - person Evan Teran; 20.04.2010

Вы можете явно закрыть stdin, stdout и stderr (с помощью fclose()).

В большинстве операционных систем динамическая память автоматически освобождается при выходе из программы. Таким образом, нет никакой практической проблемы с наличием невыпущенных буферов (есть косметическая проблема, заключающаяся в том, что эти невыпущенные буферы загрязняют вывод Valgrind). Я бы посоветовал использовать статические буферы, если вы чувствуете необходимость вызывать setvbuf() при стандартном вводе или выводе. Статические буферы не нужно выделять или освобождать, и они подходят здесь, поскольку есть только три стандартных потока, и вы беспокоитесь о ситуации, когда эти потоки остаются открытыми до завершения программы.

person Thomas Pornin    schedule 19.04.2010
comment
Загрязненный отладочный вывод — это проблема, потому что вы привыкли замалчивать вывод и можете легко пропустить настоящую ошибку. - person Mark Ransom; 20.04.2010
comment
@Thomas: есть ли гарантия, что файлы будут закрыты до освобождения памяти? - person outis; 20.04.2010
comment
@outis: в операционных системах, где память освобождается автоматически, она высвобождается атомарно, в момент, когда потоки выполнения перестают существовать. Нет ничего, что могло бы запуститься после этого, в частности, никакого кода libc, который мог бы (ошибочно) получить доступ к освобожденному буферу. Короче просто работает. Там все еще могут быть открытые файлы и буферизованные данные, но в ядре ОС, за пределами досягаемости прикладного кода. - person Thomas Pornin; 20.04.2010