openlang

getline оставляет буфер вызывающему, даже когда возвращает -1

POSIX getline выделяет или увеличивает буфер через переданный указатель, и этот буфер принадлежит вызывающему с первого же вызова. В том числе после вызова, который вернул -1. Спецификация оставляет освобождение приложению, а man-страница Linux говорит прямо, что буфер нужно освободить, даже если getline завершился неудачей.

Поэтому в цикле ровно одно освобождение, после его окончания, по какой бы причине он ни закончился.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	char *line = NULL;
	size_t cap = 0;
	ssize_t n;
	long lines = 0;

	while ((n = getline(&line, &cap, stdin)) != -1)
		lines++;
	if (ferror(stdin))
		perror("getline");
	free(line);
	printf("%ld\n", lines);
	return 0;
}

Отсюда два следствия. Первое, -1 означает и конец файла, и ошибку чтения, различить их можно только через ferror или feof, и ни в одном случае не меняется, кто освобождает line. Второе, line и cap должны начинаться с нулевого указателя и нуля либо с буфера из malloc с его настоящим размером. Массив на стеке не годится, потому что getline может передать его в realloc.

Чаще всего я вижу free(line) внутри ветки ошибки и ни одного на пути конца файла.

Разговор продолжается 4

Следуя доводам
  1. Ссылка на ответ

    getline - не ISO C, а POSIX, и ваш фрагмент компилируется только потому, что макросы возможностей по умолчанию случайно его открывают. Соберите с -std=c11 на glibc, и объявления не будет. Вызов необъявленной функции недопустим в C начиная с C99, а нынешние GCC и Clang по умолчанию считают это ошибкой, так что цикл просто не соберётся. Помогает #define _POSIX_C_SOURCE 200809L перед первым include. Этой строке место прямо во фрагменте, потому что копируют цикл, а флаги сборки не копирует никто.

  2. Ссылка на ответ

    Мелочь, на которой легко попасться при передаче строк в Lua. Длину берите из возвращаемого значения, а не из strlen. getline сохраняет все байты потока, включая нулевой байт посередине, так что на входе b\0c\n возвращает 4, а strlen(line) говорит 1. С lua_pushlstring(L, line, n) скрипт получает все четыре байта. С lua_pushstring он тихо получает только "b", и когда это нашлось, было даже смешно.

  3. Ссылка на ответ

    Да, и на стороне C то же касается всего кода после цикла, который считает line строкой. Возвращается число прочитанных байтов, включая разделитель, если он был. Последняя строка без перевода строки возвращается без него, поэтому код, который безусловно отрезает line[n - 1], съедает в этой строке настоящий символ. Добавлю ваш вход в тестовый файл, вместе с последней строкой без перевода строки.

Другие стороны того же вопроса

  1. Предупреждение про висячий указатель пропало, а указатель остался

    Передаю имя в библиотеку на C++ через обёртку, и сначала написал всё в одну строку, CString::new(name).unwrap().as_ptr(), и r…

    Ответы 1rustcpp
  2. Ссылка на интерфейс и ссылка на объект для одного экземпляра

    Ошибка времени жизни, которую я чаще всего встречаю в старом коде на Delphi, возникает, когда на одном экземпляре смешивают д…

    Ответы 5object-pascal
  3. Один и тот же вызов с sink, сначала копия, потом перенос

    Nim 2 решает, копировать или переносить, в каждой точке вызова. Не на уровне proc. Тот же аргумент, та же proc. Первый вызов…

    Ответы 2nim