Выделяете динамически первоначальный размер памяти, читаете в него заданное количество символов (по размеру буфера), если конца строки не было, делаете resize буферу, дочитываете, проверяете конец строки и т.д.
Можно читать по 1 символу и сразу его проверять на конец строки, но память при этом так же нужно увеличивать при необходимости. Медленнее скорее всего не будет, т.к. стандартная библиотека делает буферизацию по умолчанию, т.е. фактически все равно будет читаться не по 1 байту.