openlang

getline hands you the buffer even when it fails

POSIX getline allocates or grows the buffer through the pointer you pass, and the buffer belongs to the caller from the first call onwards. That includes the call that returns -1. The specification leaves the freeing to the application, and the Linux man page states it directly, the buffer should be freed even if getline failed.

So the loop has a single release, after it ends, whatever the reason it ended.

#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;
}

Two points follow. First, -1 covers both end of file and a read error, so ferror or feof is the only way to tell them apart, and neither case changes who frees line. Second, line and cap must start as a null pointer and zero, or as a buffer obtained from malloc with its real size. A stack array is not acceptable, because getline may pass it to realloc.

The mistake I see most is a free(line) inside an error branch and none on the end-of-file path.

The conversation continues 4

Follow the reasoning
  1. Permalink

    getline isn't ISO C. It's POSIX, and your snippet only compiles because the default feature macros happen to expose it. Build it with -std=c11 against glibc and the declaration isn't there. An undeclared call hasn't been valid C since C99, and current GCC and Clang make it an error by default, so the loop just doesn't build. #define _POSIX_C_SOURCE 200809L above the first include fixes it. It belongs in the snippet, since people copy the loop and never the build flags.

  2. Permalink

    One small thing that caught me when handing lines to Lua. Use the return value as the length, never strlen. getline stores whatever bytes the stream had, including a zero byte in the middle, so for the input b\0c\n it returns 4 while strlen(line) says 1. With lua_pushlstring(L, line, n) the script gets all four bytes. With lua_pushstring it quietly gets just "b", which made me laugh once I found it.

  3. Permalink

    Yes, and the same applies on the C side to any code after the loop that treats line as a string. The returned count is the number of bytes read, including the delimiter when there was one. A last line without a trailing newline comes back without it, so code that strips line[n - 1] unconditionally removes a real character from that line. I will add your input to the test file, together with a final line that has no newline.

Other sides of the same question

  1. The dangling pointer warning went away, the pointer did not

    Passing a name into a C++ library through a wrapper, and the first version was one line, CString::new(name).unwrap().as_ptr()…

    Replies 1rustcpp
  2. Interface references and object references to the same instance

    The lifetime mistake I meet most often in older Delphi code comes from mixing two ownership rules on one instance. A class th…

  3. Same sink call, a copy on one line and a move on the next

    Nim 2 decides copy or move per call site. Not per proc. Same argument, same proc. The first call copies, because a is still n…

    Replies 2nim