printf - fonte de erros? [fechadas]

9

Estou usando muito printfpara fins de rastreamento / log no meu código, descobri que é uma fonte de erro de programação. Sempre achei o operador de inserção ( <<) uma coisa estranha, mas estou começando a pensar que, ao usá-lo, poderia evitar alguns desses erros.

Alguém já teve uma revelação semelhante ou estou apenas agarrando palha aqui?

Alguns tiram pontos

  • Minha linha de pensamento atual é que a segurança de tipo supera qualquer benefício do uso de printf. O verdadeiro problema é a cadeia de formatação e o uso de funções variadas não seguras para tipos.
  • Talvez eu não esteja usando <<as variantes do fluxo de saída stl, mas certamente analisarei o uso de um mecanismo de segurança de tipo muito semelhante.
  • Grande parte do rastreamento / registro é condicional, mas eu gostaria de sempre executar o código para não perder bugs nos testes apenas porque é um ramo raramente utilizado.
John Leidegren
fonte
4
printfno mundo C ++? Estou faltando alguma coisa aqui?
User827992
10
@ user827992: Está faltando o fato de que o padrão C ++ inclui a biblioteca do padrão C por referência? É perfeitamente legal usar printfem C ++. (Se é uma boa ideia é outra questão.)
Keith Thompson
2
@ user827992: printftem algumas vantagens; veja minha resposta.
9308 Keith Thompson
11
Esta questão é bastante limítrofe. As perguntas "O que vocês acham" geralmente são fechadas.
dbracey
11
@vitaut eu acho (obrigado pela dica). Estou um pouco perplexo com a moderação agressiva. Realmente não promove discussões interessantes sobre situações de programação, das quais eu gostaria mais.
31415 John Lidegren

Respostas:

2

printf, especialmente nos casos em que você pode se preocupar com desempenho (como sprintf e fprintf), é um truque realmente estranho. Surpreende-me constantemente que as pessoas que usam C ++ devido a uma sobrecarga de desempenho minúscula relacionada a funções virtuais então defendam o io do C.

Sim, para descobrir o formato de nossa saída, algo que podemos saber 100% em tempo de compilação, vamos analisar uma string de formato em tempo de execução dentro de uma tabela de salto maciçamente estranha usando códigos de formato inescrutáveis!

É claro que esses códigos de formato não poderiam ser criados para corresponder aos tipos que representam, isso seria muito fácil ... e você será lembrado toda vez que você pesquisar se é% llg ou% lg que esse idioma (com forte tipificação) faz com que você descobrir tipos manualmente para imprimir / digitalizar algo, E foi projetado para processadores anteriores a 32 bits.

Admito que o manuseio do C ++ da largura e precisão do formato é volumoso e pode usar um pouco de açúcar sintático, mas isso não significa que você precise defender o bizarro hack que é o principal sistema io de C. O básico absoluto é bastante fácil em qualquer idioma (embora você provavelmente deva usar algo como uma função de erro / fluxo de erro personalizados para código de depuração de qualquer maneira), os casos moderados são semelhantes a regex em C (fácil de escrever, difícil de analisar / depurar) ) e os casos complexos impossíveis em C.

(Se você usar os contêineres padrão, escreva para si mesmo algumas sobrecargas << do operador de modelo rápido que permitem fazer coisas como std::cout << my_list << "\n";para depuração, onde my_list é do tipo list<vector<pair<int,string> > >.)

jkerian
fonte
11
O problema da biblioteca C ++ padrão é que a maioria das encarnações é implementada operator<<(ostream&, T)chamando ... bem sprintf! O desempenho de sprintfnão é o ideal, mas, devido a isso, o desempenho dos iostreams geralmente é ainda pior.
Jan Hudec
@JanHudec: Isso não é verdade há cerca de uma década neste momento. A impressão real é feita com as mesmas chamadas de sistema subjacentes, e as implementações de C ++ costumam chamar as bibliotecas C para isso ... mas isso não é a mesma coisa que rotear std :: cout através de printf.
jkerian
16

A mistura de saída no estilo C printf()(ou puts()ou putchar()ou ...) com saída no estilo C ++ std::cout << ...pode não ser segura. Se bem me lembro, eles podem ter mecanismos de buffer separados, portanto a saída pode não aparecer na ordem desejada. (Como o AProgrammer menciona em um comentário, sync_with_stdiotrata disso).

printf()é fundamentalmente inseguro. O tipo esperado para um argumento é determinado pela sequência de caracteres de formato ( "%d"requer um intou algo que promova int, "%s"requer um char*que deve apontar para uma sequência de estilo C terminada corretamente etc.), mas passar o tipo errado de argumento resulta em comportamento indefinido , não é um erro diagnosticável. Alguns compiladores, como o gcc, fazem um trabalho razoavelmente bom de aviso sobre incompatibilidades de tipo, mas podem fazê-lo apenas se a sequência de formatação for literal ou for conhecida em tempo de compilação (que é o caso mais comum) - e avisos não são exigidos pelo idioma. Se você passar o tipo errado de argumento, coisas arbitrariamente ruins podem acontecer.

A E / S de fluxo do C ++, por outro lado, é muito mais segura quanto ao tipo, pois o <<operador está sobrecarregado para muitos tipos diferentes. std::cout << xnão precisa especificar o tipo de x; o compilador irá gerar o código certo para qualquer tipo x.

Por outro lado, printfas opções de formatação são muito mais convenientes. Se eu quiser imprimir um valor de ponto flutuante com 3 dígitos após o ponto decimal, posso usar "%.3f"- e isso não afeta outros argumentos, mesmo dentro da mesma printfchamada. Os C ++ setprecision, por outro lado, afetam o estado do fluxo e podem atrapalhar a saída posterior, se você não tomar muito cuidado para restaurar o fluxo ao estado anterior. (Esta é minha irritação pessoal; se estiver faltando alguma maneira limpa de evitá-la, comente.)

Ambos têm vantagens e desvantagens. A disponibilidade de printfé particularmente útil se você tiver um plano de fundo em C e estiver mais familiarizado com ele, ou se estiver importando o código-fonte em um programa em C ++. std::cout << ...é mais idiomático para C ++ e não requer muito cuidado para evitar incompatibilidades de tipo. Ambos são C ++ válidos (o padrão C ++ inclui a maior parte da biblioteca padrão C por referência).

É provavelmente melhor usar std::cout << ...para o bem de outros programadores C ++ que podem trabalhar em seu código, mas você pode usar qualquer um - especialmente no código de rastreamento que você vai jogar fora.

E é claro que vale a pena gastar algum tempo aprendendo a usar depuradores (mas isso pode não ser viável em alguns ambientes).

Keith Thompson
fonte
Nenhuma menção de mistura na pergunta original.
dbracey
11
@dbracey: Não, mas achei que valeria a pena mencionar como uma possível desvantagem printf.
9308 Keith Thompson
6
Para o problema de sincronização, consulte std::ios_base::sync_with_stdio.
AProgrammer
11
+1 O uso de std :: cout para imprimir informações de depuração em um aplicativo multithread é 100% inútil. Pelo menos na impressão, as coisas não são tão prováveis ​​de serem intercaladas e inseparáveis ​​pelo homem ou pela máquina.
James
@ James: Isso é porque std::coutusa uma chamada separada para cada item que é impresso? Você pode contornar isso coletando uma linha de saída em uma string antes de imprimi-la. E é claro que você também pode imprimir um item de cada vez printf; é apenas mais conveniente imprimir uma linha (ou mais) em uma chamada.
Keith Thompson
2

É provável que seu problema venha da mistura de dois gerenciadores de saída padrão muito diferentes, cada um dos quais com sua própria agenda para o pobre e pequeno STDOUT. Você não recebe garantias sobre como elas são implementadas, e é perfeitamente possível que elas definam opções conflitantes de descritores de arquivos, ambas tentem fazer coisas diferentes, etc. Além disso, os operadores de inserção têm um grande problema printf: printfpermitem fazer isso:

printf("%d", SomeObject);

Considerando <<que não.

Nota: Para depuração, você não usa printfou cout. Você usa fprintf(stderr, ...)e cerr.

Linuxios
fonte
Nenhuma menção de mistura na pergunta original.
dbracey
É claro que você pode imprimir o endereço de um objeto, mas a grande diferença é que isso printfnão é seguro para o tipo e eu estou pensando atualmente que esse tipo de segurança supera qualquer benefício do uso printf. O problema é realmente a string de formato e a função variadica não segura para tipos.
John Leidegren
@ JohnLeidegren: Mas e se não SomeObjectfor um ponteiro? Você receberá dados binários arbitrários que o compilador decide representar SomeObject.
Linuxios 17/08/12
Eu acho que li sua resposta ao contrário ... nvm.
John Leidegren
1

Existem muitos grupos - por exemplo, o google - que não gostam de fluxos.

http://google-styleguide.googlecode.com/svn/trunk/cppguide.xml#Streams

(Abra o triângulo para que você possa ver a discussão.) Acho que o guia de estilo do Google C ++ tem MUITO conselho sensato.

Penso que a desvantagem é que os fluxos são mais seguros, mas o printf é mais claro de ler (e mais fácil de obter exatamente a formatação desejada).

dbracey
fonte
2
O guia de estilo do Google é bom, mas contém alguns itens que não são adequados para um guia de uso geral . (o que é bom, porque, afinal, é o guia do Google para código em execução no / para o Google.) #
Martin Ba
1

printfpode causar erros devido à falta de segurança do tipo. Existem algumas maneiras de resolver isso sem mudar para iostreamo <<operador e formatação mais complicada:

  • Alguns compiladores (como GCC e Clang) opcionalmente podem verificar as printfseqüências de caracteres de formato em relação aos printfargumentos e podem exibir avisos como os seguintes, se não corresponderem.
    aviso: a conversão especifica o tipo 'int', mas o argumento tem o tipo 'char *'
  • O script typesafeprintf pode pré-processar suas printfchamadas -style para torná-las seguras.
  • Bibliotecas como Boost.Format e FastFormat permitem que você use printfcadeias de formato semelhantes (em particular, as Boost.Format são quase idênticas a printf), mantendo iostreams'segurança de tipo e extensibilidade de tipo.
Josh Kelley
fonte
1

A sintaxe Printf é basicamente boa, menos algumas das digitações obscuras. Se você acha errado, por que C #, Python e outras linguagens usam a construção muito semelhante? O problema em C ou C ++: não faz parte de um idioma e, portanto, não é verificado pelo compilador quanto à sintaxe correta (*) e não é decomposto em uma série de chamadas nativas se estiver otimizando a velocidade. Observe que, se otimizar o tamanho, as chamadas printf podem se tornar mais eficientes! A sintaxe do streaming em C ++ não é nada boa. Funciona, a segurança de tipo está lá, mas a sintaxe detalhada ... bleh. Quero dizer, eu uso, mas sem alegria.

(*) alguns compiladores fazem essa verificação e quase todas as ferramentas de análise estática (eu uso o Lint e nunca tive problemas com o printf desde então).

MaR
fonte
11
Existe o Boost.Format que combina a sintaxe conveniente ( format("fmt") % arg1 % arg2 ...;) com a segurança de tipo. Ao custo de um pouco mais de desempenho, porque gera chamadas sequenciais que geram internamente chamadas sprintf em muitas implementações.
Jan Hudec
0

printfé, na minha opinião, uma ferramenta de saída muito mais flexível para lidar com variáveis ​​do que qualquer saída de fluxo CPP. Por exemplo:

printf ( "%d in ANSI = %c\n", j, j ); /* Perfectly valid... if a char ISN'T printing right, I'd just check the integer value to make sure it was okay. */

No entanto, onde você pode querer usar o <<operador CPP é quando você o sobrecarrega para um método específico ... por exemplo, para obter um despejo de um objeto que contém os dados de uma pessoa em particular, PersonData....

ostream &operator<<(ostream &stream, PersonData obj)
{
 stream << "\nName: " << name << endl;
 stream << " Number: " << phoneNumber << endl;
 stream << " Age: " << age << endl;
 return stream;
}

Para isso, seria muito mais eficaz dizer (supondo que aseja um objeto de PersonData)

std::cout << a;

do que:

printf ( "Name: %s\n Number: %s\n Age: %d\n", a.name, a.number, a.age );

O primeiro está muito mais alinhado com o princípio do encapsulamento (não há necessidade de conhecer as especificidades, variáveis ​​de membros particulares) e também é mais fácil de ler.

Aviator45003
fonte
0

Você não deveria usar printf no C ++. Sempre. O motivo é, como você observou corretamente, que é uma fonte de bugs e o fato de imprimir tipos personalizados e em C ++ quase tudo deve ser de tipos personalizados é um problema. A solução C ++ é os fluxos.

No entanto, existe um problema crítico que torna os fluxos inadequados para qualquer saída visível para o usuário! O problema são as traduções. O exemplo de empréstimo do manual gettext diz que você deseja escrever:

cout << "String '" << str << "' has " << str.size() << " characters\n";

Agora o tradutor alemão aparece e diz: Ok, em alemão, a mensagem deve ser

n Zeichen lang ist die Zeichenkette ' s '

E agora você está com problemas, porque ele precisa que as peças sejam embaralhadas. Deve-se dizer que mesmo muitas implementações printftêm problemas com isso. A menos que eles suportem a extensão para que você possa usar

printf("%2$d Zeichen lang ist die Zeichenkette '%1$s'", ...);

O Boost.Format suporta formatos no estilo printf e possui esse recurso. Então você escreve:

cout << format("String '%1' has %2 characters\n") % str % str.size();

Infelizmente, isso acarreta um pouco de penalidade no desempenho, porque internamente cria uma cadeia de caracteres e usa o <<operador para formatar cada bit e, em muitas implementações, o <<operador chama internamente sprintf. Eu suspeito que uma implementação mais eficiente seria possível se realmente desejado.

Jan Hudec
fonte
-1

Você está fazendo muito trabalho inútil, além do stlmal ou não, depure seu código com uma série deprintf apenas mais 1 nível possível de falhas.

Basta usar um depurador e ler algo sobre exceções e como capturá-las e lançá-las; tente não ser mais detalhado do que realmente precisa.

PS

printf é usado em C, para o C ++ você tem std::cout

user827992
fonte
Você não usa rastreamento / log em vez de um depurador.
John Leidegren