A justificativa por trás das funções da biblioteca C nunca define errno como zero

9

O padrão C exige que nenhuma função da biblioteca padrão C seja definida errnocomo zero. Por que exatamente é isso?

Eu pude entender que é útil chamar várias funções e verificar apenas errnoapós a última - por exemplo:

errno = 0;
double x = strtod(str1, NULL);
long y = strtol(str2, NULL);
if (errno)
    // either "strtod" or "strtol" failed
else
    // both succeeded

No entanto, isso não é considerado "má prática"? Desde que você só está verificando errnono final, você só sabe que uma das funções fez falhar, mas não que função falhou. É simplesmente saber que algo falhou o suficiente para a maioria dos programas práticos?

Tentei procurar vários documentos C Rationale, mas muitos deles não têm muitos detalhes <errno.h>.

Drew McGowen
fonte
11
Eu acho que é "não pague pelo que você não quer". Se você se preocupa errno, pode sempre defini-lo como zero.
precisa saber é o seguinte
2
Outra coisa a ter em atenção é que uma função pode definir errnoum valor diferente de zero, mesmo que seja bem-sucedida. (Pode chamar alguma outra função que não, mas isso não constitui uma falha na função externa.)
Keith Thompson

Respostas:

10

A biblioteca C não define errnocomo 0 por razões históricas 1 . O POSIX não afirma mais que suas bibliotecas não alterarão o valor em caso de sucesso, e a nova página de manual do Linuxerrno.h reflete isso:

O <errno.h>arquivo de cabeçalho define a variável inteira errno, que é definida por chamadas do sistema e algumas funções da biblioteca no caso de um erro para indicar o que deu errado. Seu valor é significativo apenas quando o valor de retorno da chamada indica um erro (ou seja, -1da maioria das chamadas do sistema; -1ou NULLda maioria das funções da biblioteca); uma função que sucede é permitida a mudança errno.

A justificativa do ANSI C afirma que o comitê considerou mais prático adotar e padronizar a prática existente de uso errno.

O mecanismo de relatório de erros centrado na configuração de errnoé geralmente considerado com tolerância, na melhor das hipóteses. Requer um `` acoplamento patológico '' entre as funções da biblioteca e faz uso de uma célula de memória gravável estática, que interfere na construção de bibliotecas compartilháveis. No entanto, o Comitê preferiu padronizar esse mecanismo existente, embora deficiente, em vez de inventar algo mais ambicioso.

Quase sempre existe uma maneira de verificar se há erros fora da verificação, se errnotiver sido definido. Verificar se errnoo conjunto foi configurado nem sempre é confiável, pois algumas chamadas exigem a chamada de uma API separada para obter o motivo do erro. Por exemplo, ferror()é usado para verificar se há um pequeno resultado de fread()ou fwrite().

Curiosamente, seu exemplo de uso strtod()é um dos casos em que errnoé necessário definir 0 antes da chamada para detectar corretamente se ocorreu um erro. Todas as strto*()funções string para number têm esse requisito, porque um valor de retorno válido é retornado mesmo diante de um erro.

errno = 0;
char *endptr;
double x = strtod(str1, &endptr);
if (endptr == str1) {
    /*...parse error */
} else if (errno == ERANGE) {
    if (x == 0) {
        /*...underflow */
    } else if (x == HUGE_VAL) {
        /*...positive overflow */
    } else if (x == -HUGE_VAL) {
        /*...negative overflow */
    } else {
        /*...unknown range error? */
    }
}

O código acima é baseado no comportamento de strtod()conforme documentado no Linux . O padrão C apenas estipula que o subfluxo não pode retornar um valor maior que o menor positivo doublee se está ou não errnodefinido como está definido para ERANGEimplementação 2 .

Na verdade, existe uma extensa redação de recomendação de certificado que recomenda sempre definir errno0 antes de uma chamada de biblioteca e verificar seu valor após a chamada indicar que ocorreu uma falha . Isso ocorre porque algumas chamadas de biblioteca serão definidas errnomesmo que a chamada em si tenha sido bem-sucedida 3 .

O valor de errnoé 0 na inicialização do programa, mas nunca é definido como 0 por nenhuma função da biblioteca. O valor de errnopode ser definido como diferente de zero por uma chamada de função de biblioteca, havendo ou não um erro, desde que o uso de errnonão esteja documentado na descrição da função no Padrão C. É significativo para um programa inspecionar o conteúdo errnosomente após um erro ter sido relatado. Mais precisamente, errnosó é significativo depois que uma função de biblioteca que define errnoerro retorna um código de erro.


1. Anteriormente, afirmei que era para evitar mascarar um erro de uma chamada anterior. Não consigo encontrar nenhuma evidência para apoiar esta afirmação. Eu também tive um printf()exemplo falso .
2. Obrigado ao @chux por apontar isso. A referência é C.11 §7.22.1.3 ¶10.
3. Apontado por @KeithThompson em um comentário.

jxh
fonte
Problema menor: parece que o fluxo insuficiente pode resultar em um resultado diferente de 0: "as funções retornam um valor cuja magnitude não é maior que o menor número positivo normalizado do tipo de retorno" C11 7.22.1.3.10.
chux - Restabelece Monica
@chux: Obrigado. Eu vou fazer uma edição. Como definir como errnoa ERANGEimplementação é definida no caso de subfluxo, na verdade não existe uma maneira portátil de detectar subfluxo. Meu código seguiu o que encontrei na página de manual do Linux no meu sistema.
JXH
Seus comentários sobre as strto*funções são exatamente o motivo pelo qual perguntei se meu exemplo seria considerado uma má prática, mas é a única maneira pela qual errnonão ser definido como zero seria útil ou até aplicável.
@DrewMcGowen: Eu acho que o único ponto que você pode entender é que errnopode ser definido mesmo no caso de sucesso, portanto, apenas usar o fato de que foi definido não é realmente um indicador suficientemente bom de que ocorreu um erro. Você deve verificar os resultados das chamadas individuais para saber se ocorreu algum erro.
precisa saber é
1

Você pode fazer a verificação de erros para as duas chamadas de função, se realmente se importa.

errno = 0;
double x = strtod(str1, NULL);
if (errno)
    // strtod"  failed
else
    // "strtod" succeeded

long y = strtol(str2, NULL);
if (errno)
    // "strtol" failed
else
    // "strtol" succeeded

Como nunca sabemos como a função mach é chamada em um processo, como a lib pode definir erros para cada função, certo?


fonte
1

Mudar arbitrariamente o errorno é análogo a 'pegar e engolir' uma exceção. Antes que houvesse exceções que se propagariam por diferentes camadas de um programa e finalmente chegassem a um ponto em que o chamador capturasse e respondesse à exceção de alguma maneira ou simplesmente passasse a bola, havia erros. Não alterar o erro, a menos que você esteja manipulando, substituindo, tratando o erro como irrelevante, é essencial para esse paradigma de propagação da manipulação de erros de primeira geração.

Andyz Smith
fonte