Executando verificação de segurança...
6

Só um detalhe: fgets lê os dados até encontrar uma quebra de linha ou ler a quantidade máxima de caracteres. Isso quer dizer que se tiver uma linha maior do que MAX_LINE_LEN, ele não lerá a linha toda.

Uma abordagem mais garantida é ir lendo os caracteres e guardando em um buffer, até encontrar a quebra de linha (ou o final do arquivo, que indicaria a última linha). Mas se o tamanho máximo for atingido antes disso, bastaria realocar para um tamanho maior. Eu me baseei neste código e adaptei para o seu caso.

Outro ponto é que não precisa ler o arquivo duas vezes (uma para ver a quantidade de linhas, outro para buscar a linha aleatória). Você pode ir lendo apenas uma vez, usando este algoritmo (que também adaptei para o seu caso). A ideia é que a cada linha você teste se 1 dividido pelo número da linha é maior que um determinado valor aleatório - mas para isso ele usa a função drand48, que retorna um float aleatório entre 0 e 1 (disponível apenas no Linux - para Windows, tem outras alternativas).

Enfim, ficou assim:

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

char *choose_random_line(FILE *f) {
    srand48(time(NULL));
    int current_max = 256; // um tamanho inicial qualquer
    char *selected = malloc(current_max);
    char *current = malloc(current_max);
    selected[0] = '\0';

    int length = 0, line_number = 0;
    int ch = 0;
    while (ch != EOF) {
        ch = fgetc(f);
        if (length == current_max) { // atingiu o tamanho máximo, realocar mais memória
            current_max *= 2; // uma estratégia comum é dobrar o tamanho
            current = realloc(current, current_max);
            selected = realloc(selected, current_max);
            // pode incluir if (selected == NULL) para verificar se conseguiu realocar, etc
        }

        if (ch == '\n' || ch == EOF) { // terminou a linha ou o arquivo
            current[length] = '\0';
            if (drand48() < 1.0 / ++line_number) { // aleatoriamente pode selecionar esta linha
                strcpy(selected, current);
            }
            length = 0;
            continue;
        }

        current[length++] = ch;
    }
    free(current);

    return selected;
}

int main(void) {
    FILE *f = fopen("texto.txt", "r");
    if (f == NULL) {
        fprintf(stderr, "Error when trying to read file.\n");
        return 1;
    }
    char *random_line = choose_random_line(f);
    printf("Linha aleatória: %s\n", random_line);
    free(random_line);
    fclose(f);
    return 0;
}
2

Muito obrigado pelo feedback. Não conhecia esse detalhe do fgets que você mencionou.

Achei interessante essa sua versão do código e com certeza seria algo que eu usaria em um caso real. Mas no meu post, eu quis deixar o mais simples possível, sem alocação de memória ou funções do header string, apenas um algoritmo para alguém que teve pouco ou nenhum contato poder entender.

De todo modo, é interessante ter uma visão mais concreta de um caso de uso. Obrigado pelo seu comentário.